diff options
Diffstat (limited to 'src/gui/itemviews')
67 files changed, 49086 insertions, 0 deletions
diff --git a/src/gui/itemviews/itemviews.pri b/src/gui/itemviews/itemviews.pri new file mode 100644 index 0000000..bbc1e98 --- /dev/null +++ b/src/gui/itemviews/itemviews.pri @@ -0,0 +1,70 @@ +# Qt gui library, itemviews + +HEADERS += \ + itemviews/qabstractitemview.h \ + itemviews/qabstractitemview_p.h \ + itemviews/qheaderview.h \ + itemviews/qlistview.h \ + itemviews/qlistview_p.h \ + itemviews/qbsptree_p.h \ + itemviews/qtableview.h \ + itemviews/qtableview_p.h \ + itemviews/qtreeview.h \ + itemviews/qtreeview_p.h \ + itemviews/qabstractitemdelegate.h \ + itemviews/qitemdelegate.h \ + itemviews/qitemselectionmodel.h \ + itemviews/qitemselectionmodel_p.h \ + itemviews/qdirmodel.h \ + itemviews/qlistwidget.h \ + itemviews/qlistwidget_p.h \ + itemviews/qtablewidget.h \ + itemviews/qtablewidget_p.h \ + itemviews/qtreewidget.h \ + itemviews/qtreewidget_p.h \ + itemviews/qwidgetitemdata_p.h \ + itemviews/qproxymodel.h \ + itemviews/qproxymodel_p.h \ + itemviews/qabstractproxymodel.h \ + itemviews/qabstractproxymodel_p.h \ + itemviews/qsortfilterproxymodel.h \ + itemviews/qitemeditorfactory.h \ + itemviews/qitemeditorfactory_p.h \ + itemviews/qstandarditemmodel.h \ + itemviews/qstandarditemmodel_p.h \ + itemviews/qstringlistmodel.h \ + itemviews/qtreewidgetitemiterator.h \ + itemviews/qdatawidgetmapper.h \ + itemviews/qfileiconprovider.h \ + itemviews/qcolumnviewgrip_p.h \ + itemviews/qcolumnview.h \ + itemviews/qcolumnview_p.h \ + itemviews/qstyleditemdelegate.h + +SOURCES += \ + itemviews/qabstractitemview.cpp \ + itemviews/qheaderview.cpp \ + itemviews/qlistview.cpp \ + itemviews/qbsptree.cpp \ + itemviews/qtableview.cpp \ + itemviews/qtreeview.cpp \ + itemviews/qabstractitemdelegate.cpp \ + itemviews/qitemdelegate.cpp \ + itemviews/qitemselectionmodel.cpp \ + itemviews/qdirmodel.cpp \ + itemviews/qlistwidget.cpp \ + itemviews/qtablewidget.cpp \ + itemviews/qtreewidget.cpp \ + itemviews/qproxymodel.cpp \ + itemviews/qabstractproxymodel.cpp \ + itemviews/qsortfilterproxymodel.cpp \ + itemviews/qitemeditorfactory.cpp \ + itemviews/qstandarditemmodel.cpp \ + itemviews/qstringlistmodel.cpp \ + itemviews/qtreewidgetitemiterator.cpp \ + itemviews/qdatawidgetmapper.cpp \ + itemviews/qfileiconprovider.cpp \ + itemviews/qcolumnview.cpp \ + itemviews/qcolumnviewgrip.cpp \ + itemviews/qstyleditemdelegate.cpp + diff --git a/src/gui/itemviews/qabstractitemdelegate.cpp b/src/gui/itemviews/qabstractitemdelegate.cpp new file mode 100644 index 0000000..e94c067 --- /dev/null +++ b/src/gui/itemviews/qabstractitemdelegate.cpp @@ -0,0 +1,387 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qabstractitemdelegate.h" + +#ifndef QT_NO_ITEMVIEWS +#include <qabstractitemmodel.h> +#include <qabstractitemview.h> +#include <qfontmetrics.h> +#include <qwhatsthis.h> +#include <qtooltip.h> +#include <qevent.h> +#include <qstring.h> +#include <qdebug.h> +#include <private/qtextengine_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QAbstractItemDelegate + + \brief The QAbstractItemDelegate class is used to display and edit + data items from a model. + + \ingroup model-view + \mainclass + + A QAbstractItemDelegate provides the interface and common functionality + for delegates in the model/view architecture. Delegates display + individual items in views, and handle the editing of model data. + + The QAbstractItemDelegate class is one of the \l{Model/View Classes} + and is part of Qt's \l{Model/View Programming}{model/view framework}. + + To render an item in a custom way, you must implement paint() and + sizeHint(). The QItemDelegate class provides default implementations for + these functions; if you do not need custom rendering, subclass that + class instead. + + We give an example of drawing a progress bar in items; in our case + for a package management program. + + \image widgetdelegate.png + + We create the \c WidgetDelegate class, which inherits from + QStyledItemDelegate. We do the drawing in the paint() function: + + \snippet doc/src/snippets/widgetdelegate.cpp 0 + + Notice that we use a QStyleOptionProgressBar and initialize its + members. We can then use the current QStyle to draw it. + + To provide custom editing, there are two approaches that can be + used. The first approach is to create an editor widget and display + it directly on top of the item. To do this you must reimplement + createEditor() to provide an editor widget, setEditorData() to populate + the editor with the data from the model, and setModelData() so that the + delegate can update the model with data from the editor. + + The second approach is to handle user events directly by reimplementing + editorEvent(). + + \sa {model-view-programming}{Model/View Programming}, QItemDelegate, + {Pixelator Example}, QStyledItemDelegate, QStyle +*/ + +/*! + \enum QAbstractItemDelegate::EndEditHint + + This enum describes the different hints that the delegate can give to the + model and view components to make editing data in a model a comfortable + experience for the user. + + \value NoHint There is no recommended action to be performed. + + These hints let the delegate influence the behavior of the view: + + \value EditNextItem The view should use the delegate to open an + editor on the next item in the view. + \value EditPreviousItem The view should use the delegate to open an + editor on the previous item in the view. + + Note that custom views may interpret the concepts of next and previous + differently. + + The following hints are most useful when models are used that cache + data, such as those that manipulate data locally in order to increase + performance or conserve network bandwidth. + + \value SubmitModelCache If the model caches data, it should write out + cached data to the underlying data store. + \value RevertModelCache If the model caches data, it should discard + cached data and replace it with data from the + underlying data store. + + Although models and views should respond to these hints in appropriate + ways, custom components may ignore any or all of them if they are not + relevant. +*/ + +/*! + \fn void QAbstractItemDelegate::commitData(QWidget *editor) + + This signal must be emitted when the \a editor widget has completed + editing the data, and wants to write it back into the model. +*/ + +/*! + \fn void QAbstractItemDelegate::closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint) + + This signal is emitted when the user has finished editing an item using + the specified \a editor. + + The \a hint provides a way for the delegate to influence how the model and + view behave after editing is completed. It indicates to these components + what action should be performed next to provide a comfortable editing + experience for the user. For example, if \c EditNextItem is specified, + the view should use a delegate to open an editor on the next item in the + model. + + \sa EndEditHint +*/ + +/*! + \fn void QAbstractItemDelegate::sizeHintChanged(const QModelIndex &index) + \since 4.4 + + This signal must be emitted when the sizeHint() of \a index changed. + + Views automatically connect to this signal and relayout items as necessary. +*/ + + +/*! + Creates a new abstract item delegate with the given \a parent. +*/ +QAbstractItemDelegate::QAbstractItemDelegate(QObject *parent) + : QObject(parent) +{ + +} + +/*! + \internal + + Creates a new abstract item delegate with the given \a parent. +*/ +QAbstractItemDelegate::QAbstractItemDelegate(QObjectPrivate &dd, QObject *parent) + : QObject(dd, parent) +{ + +} + +/*! + Destroys the abstract item delegate. +*/ +QAbstractItemDelegate::~QAbstractItemDelegate() +{ + +} + +/*! + \fn void QAbstractItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const = 0; + + This pure abstract function must be reimplemented if you want to + provide custom rendering. Use the \a painter and style \a option to + render the item specified by the item \a index. + + If you reimplement this you must also reimplement sizeHint(). +*/ + +/*! + \fn QSize QAbstractItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const = 0 + + This pure abstract function must be reimplemented if you want to + provide custom rendering. The options are specified by \a option + and the model item by \a index. + + If you reimplement this you must also reimplement paint(). +*/ + +/*! + Returns the editor to be used for editing the data item with the + given \a index. Note that the index contains information about the + model being used. The editor's parent widget is specified by \a parent, + and the item options by \a option. + + The base implementation returns 0. If you want custom editing you + will need to reimplement this function. + + The returned editor widget should have Qt::StrongFocus; + otherwise, \l{QMouseEvent}s received by the widget will propagate + to the view. The view's background will shine through unless the + editor paints its own background (e.g., with + \l{QWidget::}{setAutoFillBackground()}). + + \sa setModelData() setEditorData() +*/ +QWidget *QAbstractItemDelegate::createEditor(QWidget *, + const QStyleOptionViewItem &, + const QModelIndex &) const +{ + return 0; +} + +/*! + Sets the contents of the given \a editor to the data for the item + at the given \a index. Note that the index contains information + about the model being used. + + The base implementation does nothing. If you want custom editing + you will need to reimplement this function. + + \sa setModelData() +*/ +void QAbstractItemDelegate::setEditorData(QWidget *, + const QModelIndex &) const +{ + // do nothing +} + +/*! + Sets the data for the item at the given \a index in the \a model + to the contents of the given \a editor. + + The base implementation does nothing. If you want custom editing + you will need to reimplement this function. + + \sa setEditorData() +*/ +void QAbstractItemDelegate::setModelData(QWidget *, + QAbstractItemModel *, + const QModelIndex &) const +{ + // do nothing +} + +/*! + Updates the geometry of the \a editor for the item with the given + \a index, according to the rectangle specified in the \a option. + If the item has an internal layout, the editor will be laid out + accordingly. Note that the index contains information about the + model being used. + + The base implementation does nothing. If you want custom editing + you must reimplement this function. +*/ +void QAbstractItemDelegate::updateEditorGeometry(QWidget *, + const QStyleOptionViewItem &, + const QModelIndex &) const +{ + // do nothing +} + +/*! + Whenever an event occurs, this function is called with the \a event + \a model \a option and the \a index that corresponds to the item being edited. + + The base implementation returns false (indicating that it has not + handled the event). +*/ +bool QAbstractItemDelegate::editorEvent(QEvent *, + QAbstractItemModel *, + const QStyleOptionViewItem &, + const QModelIndex &) +{ + // do nothing + return false; +} + +/*! + \obsolete + + Use QFontMetrics::elidedText() instead. + + \oldcode + QFontMetrics fm = ... + QString str = QAbstractItemDelegate::elidedText(fm, width, mode, text); + \newcode + QFontMetrics fm = ... + QString str = fm.elidedText(text, mode, width); + \endcode +*/ + +QString QAbstractItemDelegate::elidedText(const QFontMetrics &fontMetrics, int width, + Qt::TextElideMode mode, const QString &text) +{ + return fontMetrics.elidedText(text, mode, width); +} + +/*! + \since 4.3 + Whenever a help event occurs, this function is called with the \a event + \a view \a option and the \a index that corresponds to the item where the + event occurs. + + Returns true if the delegate can handle the event; otherwise returns false. + A return value of true indicates that the data obtained using the index had + the required role. + + For QEvent::ToolTip and QEvent::WhatsThis events that were handled successfully, + the relevant popup may be shown depending on the user's system configuration. + + \sa QHelpEvent +*/ +// ### Qt 5: Make this a virtual non-slot function +bool QAbstractItemDelegate::helpEvent(QHelpEvent *event, + QAbstractItemView *view, + const QStyleOptionViewItem &option, + const QModelIndex &index) +{ + Q_UNUSED(option); + + if (!event || !view) + return false; + switch (event->type()) { +#ifndef QT_NO_TOOLTIP + case QEvent::ToolTip: { + QHelpEvent *he = static_cast<QHelpEvent*>(event); + QVariant tooltip = index.data(Qt::ToolTipRole); + if (qVariantCanConvert<QString>(tooltip)) { + QToolTip::showText(he->globalPos(), tooltip.toString(), view); + return true; + } + break;} +#endif +#ifndef QT_NO_WHATSTHIS + case QEvent::QueryWhatsThis: { + if (index.data(Qt::WhatsThisRole).isValid()) + return true; + break; } + case QEvent::WhatsThis: { + QHelpEvent *he = static_cast<QHelpEvent*>(event); + QVariant whatsthis = index.data(Qt::WhatsThisRole); + if (qVariantCanConvert<QString>(whatsthis)) { + QWhatsThis::showText(he->globalPos(), whatsthis.toString(), view); + return true; + } + break ; } +#endif + default: + break; + } + return false; +} + +QT_END_NAMESPACE + +#endif // QT_NO_ITEMVIEWS diff --git a/src/gui/itemviews/qabstractitemdelegate.h b/src/gui/itemviews/qabstractitemdelegate.h new file mode 100644 index 0000000..fe9aca7 --- /dev/null +++ b/src/gui/itemviews/qabstractitemdelegate.h @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTITEMDELEGATE_H +#define QABSTRACTITEMDELEGATE_H + +#include <QtCore/qobject.h> +#include <QtGui/qstyleoption.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_ITEMVIEWS + +class QPainter; +class QModelIndex; +class QAbstractItemModel; +class QAbstractItemView; +class QHelpEvent; + +class Q_GUI_EXPORT QAbstractItemDelegate : public QObject +{ + Q_OBJECT + +public: + + enum EndEditHint { + NoHint, + EditNextItem, + EditPreviousItem, + SubmitModelCache, + RevertModelCache + }; + + explicit QAbstractItemDelegate(QObject *parent = 0); + virtual ~QAbstractItemDelegate(); + + // painting + virtual void paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const = 0; + + virtual QSize sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const = 0; + + // editing + virtual QWidget *createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + virtual void setEditorData(QWidget *editor, const QModelIndex &index) const; + + virtual void setModelData(QWidget *editor, + QAbstractItemModel *model, + const QModelIndex &index) const; + + virtual void updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + // for non-widget editors + virtual bool editorEvent(QEvent *event, + QAbstractItemModel *model, + const QStyleOptionViewItem &option, + const QModelIndex &index); + + static QString elidedText(const QFontMetrics &fontMetrics, int width, + Qt::TextElideMode mode, const QString &text); + +public Q_SLOTS: + bool helpEvent(QHelpEvent *event, + QAbstractItemView *view, + const QStyleOptionViewItem &option, + const QModelIndex &index); + +Q_SIGNALS: + void commitData(QWidget *editor); + void closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint = NoHint); + void sizeHintChanged(const QModelIndex &); + +protected: + QAbstractItemDelegate(QObjectPrivate &, QObject *parent = 0); +private: + Q_DISABLE_COPY(QAbstractItemDelegate) +}; + +#endif // QT_NO_ITEMVIEWS + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QABSTRACTITEMDELEGATE_H diff --git a/src/gui/itemviews/qabstractitemview.cpp b/src/gui/itemviews/qabstractitemview.cpp new file mode 100644 index 0000000..c7ba95d --- /dev/null +++ b/src/gui/itemviews/qabstractitemview.cpp @@ -0,0 +1,3918 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qabstractitemview.h" + +#ifndef QT_NO_ITEMVIEWS +#include <qpointer.h> +#include <qapplication.h> +#include <qclipboard.h> +#include <qpainter.h> +#include <qstyle.h> +#include <qdrag.h> +#include <qevent.h> +#include <qscrollbar.h> +#include <qwhatsthis.h> +#include <qtooltip.h> +#include <qdatetime.h> +#include <qlineedit.h> +#include <qspinbox.h> +#include <qstyleditemdelegate.h> +#include <private/qabstractitemview_p.h> +#include <private/qabstractitemmodel_p.h> +#ifndef QT_NO_ACCESSIBILITY +#include <qaccessible.h> +#endif + +QT_BEGIN_NAMESPACE + +QAbstractItemViewPrivate::QAbstractItemViewPrivate() + : model(QAbstractItemModelPrivate::staticEmptyModel()), + itemDelegate(0), + selectionModel(0), + selectionMode(QAbstractItemView::ExtendedSelection), + selectionBehavior(QAbstractItemView::SelectItems), + currentlyCommittingEditor(0), + pressedModifiers(Qt::NoModifier), + pressedPosition(QPoint(-1, -1)), + viewportEnteredNeeded(false), + state(QAbstractItemView::NoState), + editTriggers(QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed), + lastTrigger(QAbstractItemView::NoEditTriggers), + tabKeyNavigation(false), +#ifndef QT_NO_DRAGANDDROP + showDropIndicator(true), + dragEnabled(false), + dragDropMode(QAbstractItemView::NoDragDrop), + overwrite(false), + dropIndicatorPosition(QAbstractItemView::OnItem), +#endif + autoScroll(true), + autoScrollMargin(16), + autoScrollCount(0), + alternatingColors(false), + textElideMode(Qt::ElideRight), + verticalScrollMode(QAbstractItemView::ScrollPerItem), + horizontalScrollMode(QAbstractItemView::ScrollPerItem), + currentIndexSet(false), + wrapItemText(false), + delayedPendingLayout(false) +{ +} + +QAbstractItemViewPrivate::~QAbstractItemViewPrivate() +{ +} + +void QAbstractItemViewPrivate::init() +{ + Q_Q(QAbstractItemView); + q->setItemDelegate(new QStyledItemDelegate(q)); + + q->verticalScrollBar()->setRange(0, 0); + q->horizontalScrollBar()->setRange(0, 0); + + QObject::connect(q->verticalScrollBar(), SIGNAL(actionTriggered(int)), + q, SLOT(verticalScrollbarAction(int))); + QObject::connect(q->horizontalScrollBar(), SIGNAL(actionTriggered(int)), + q, SLOT(horizontalScrollbarAction(int))); + QObject::connect(q->verticalScrollBar(), SIGNAL(valueChanged(int)), + q, SLOT(verticalScrollbarValueChanged(int))); + QObject::connect(q->horizontalScrollBar(), SIGNAL(valueChanged(int)), + q, SLOT(horizontalScrollbarValueChanged(int))); + + viewport->setBackgroundRole(QPalette::Base); + + doDelayedItemsLayout(); + + q->setAttribute(Qt::WA_InputMethodEnabled); +} + +/*! + \class QAbstractItemView + + \brief The QAbstractItemView class provides the basic functionality for + item view classes. + + \ingroup model-view + \mainclass + + QAbstractItemView class is the base class for every standard view + that uses a QAbstractItemModel. QAbstractItemView is an abstract + class and cannot itself be instantiated. It provides a standard + interface for interoperating with models through the signals and + slots mechanism, enabling subclasses to be kept up-to-date with + changes to their models. This class provides standard support for + keyboard and mouse navigation, viewport scrolling, item editing, + and selections. + + The QAbstractItemView class is one of the \l{Model/View Classes} + and is part of Qt's \l{Model/View Programming}{model/view framework}. + + The view classes that inherit QAbstractItemView only need + to implement their own view-specific functionality, such as + drawing items, returning the geometry of items, finding items, + etc. + + QAbstractItemView provides common slots such as edit() and + setCurrentIndex(). Many protected slots are also provided, including + dataChanged(), rowsInserted(), rowsAboutToBeRemoved(), selectionChanged(), + and currentChanged(). + + The root item is returned by rootIndex(), and the current item by + currentIndex(). To make sure that an item is visible use + scrollTo(). + + Some of QAbstractItemView's functions are concerned with + scrolling, for example setHorizontalScrollMode() and + setVerticalScrollMode(). To set the range of the scroll bars, you + can, for example, reimplement the view's resizeEvent() function: + + \snippet doc/src/snippets/code/src_gui_itemviews_qabstractitemview.cpp 0 + + Note that the range is not updated until the widget is shown. + + Several other functions are concerned with selection control; for + example setSelectionMode(), and setSelectionBehavior(). This class + provides a default selection model to work with + (selectionModel()), but this can be replaced by using + setSelectionModel() with an instance of QItemSelectionModel. + + For complete control over the display and editing of items you can + specify a delegate with setItemDelegate(). + + QAbstractItemView provides a lot of protected functions. Some are + concerned with editing, for example, edit(), and commitData(), + whilst others are keyboard and mouse event handlers. + + \note If you inherit QAbstractItemView and intend to update the contents + of the viewport, you should use viewport->update() instead of + \l{QWidget::update()}{update()} as all painting operations take place on the + viewport. + + \sa {View Classes}, {Model/View Programming}, QAbstractItemModel, {Chart Example} +*/ + +/*! + \enum QAbstractItemView::SelectionMode + + This enum indicates how the view responds to user selections: + + \value SingleSelection When the user selects an item, any already-selected + item becomes unselected, and the user cannot unselect the selected item by + clicking on it. + + \value ContiguousSelection When the user selects an item in the usual way, + the selection is cleared and the new item selected. However, if the user + presses the Shift key while clicking on an item, all items between the + current item and the clicked item are selected or unselected, depending on + the state of the clicked item. + + \value ExtendedSelection When the user selects an item in the usual way, + the selection is cleared and the new item selected. However, if the user + presses the Ctrl key when clicking on an item, the clicked item gets + toggled and all other items are left untouched. If the user presses the + Shift key while clicking on an item, all items between the current item + and the clicked item are selected or unselected, depending on the state of + the clicked item. Multiple items can be selected by dragging the mouse over + them. + + \value MultiSelection When the user selects an item in the usual way, the + selection status of that item is toggled and the other items are left + alone. Multiple items can be toggled by dragging the mouse over them. + + \value NoSelection Items cannot be selected. + + The most commonly used modes are SingleSelection and ExtendedSelection. +*/ + +/*! + \enum QAbstractItemView::SelectionBehavior + + \value SelectItems Selecting single items. + \value SelectRows Selecting only rows. + \value SelectColumns Selecting only columns. +*/ + +/*! + \enum QAbstractItemView::ScrollHint + + \value EnsureVisible Scroll to ensure that the item is visible. + \value PositionAtTop Scroll to position the item at the top of the + viewport. + \value PositionAtBottom Scroll to position the item at the bottom of the + viewport. + \value PositionAtCenter Scroll to position the item at the center of the + viewport. +*/ + + +/*! + \enum QAbstractItemView::EditTrigger + + This enum describes actions which will initiate item editing. + + \value NoEditTriggers No editing possible. + \value CurrentChanged Editing start whenever current item changes. + \value DoubleClicked Editing starts when an item is double clicked. + \value SelectedClicked Editing starts when clicking on an already selected + item. + \value EditKeyPressed Editing starts when the platform edit key has been + pressed over an item. + \value AnyKeyPressed Editing starts when any key is pressed over an item. + \value AllEditTriggers Editing starts for all above actions. +*/ + +/*! + \enum QAbstractItemView::CursorAction + + This enum describes the different ways to navigate between items, + \sa moveCursor() + + \value MoveUp Move to the item above the current item. + \value MoveDown Move to the item below the current item. + \value MoveLeft Move to the item left of the current item. + \value MoveRight Move to the item right of the current item. + \value MoveHome Move to the top-left corner item. + \value MoveEnd Move to the bottom-right corner item. + \value MovePageUp Move one page up above the current item. + \value MovePageDown Move one page down below the current item. + \value MoveNext Move to the item after the current item. + \value MovePrevious Move to the item before the current item. +*/ + +/*! + \enum QAbstractItemView::State + + Describes the different states the view can be in. This is usually + only interesting when reimplementing your own view. + + \value NoState The is the default state. + \value DraggingState The user is dragging items. + \value DragSelectingState The user is selecting items. + \value EditingState The user is editing an item in a widget editor. + \value ExpandingState The user is opening a branch of items. + \value CollapsingState The user is closing a branch of items. + \value AnimatingState The item view is performing an animation. +*/ + +/*! + \since 4.2 + \enum QAbstractItemView::ScrollMode + + \value ScrollPerItem The view will scroll the contents one item at a time. + \value ScrollPerPixel The view will scroll the contents one pixel at a time. +*/ + +/*! + \fn QRect QAbstractItemView::visualRect(const QModelIndex &index) const = 0 + Returns the rectangle on the viewport occupied by the item at \a index. + + If your item is displayed in several areas then visualRect should return + the primary area that contains index and not the complete area that index + might encompasses, touch or cause drawing. + + In the base class this is a pure virtual function. + + \sa indexAt(), visualRegionForSelection() +*/ + +/*! + \fn void QAbstractItemView::scrollTo(const QModelIndex &index, ScrollHint hint) = 0 + + Scrolls the view if necessary to ensure that the item at \a index + is visible. The view will try to position the item according to the given \a hint. + + In the base class this is a pure virtual function. +*/ + +/*! + \fn QModelIndex QAbstractItemView::indexAt(const QPoint &point) const = 0 + + Returns the model index of the item at the viewport coordinates \a point. + + In the base class this is a pure virtual function. + + \sa visualRect() +*/ + +/*! + \fn void QAbstractItemView::activated(const QModelIndex &index) + + This signal is emitted when the item specified by \a index is + activated by the user. How to activate items depends on the + platform; e.g., by single- or double-clicking the item, or by + pressing the Return or Enter key when the item is current. + + \sa clicked(), doubleClicked(), entered(), pressed() +*/ + +/*! + \fn void QAbstractItemView::entered(const QModelIndex &index) + + This signal is emitted when the mouse cursor enters the item + specified by \a index. + Mouse tracking needs to be enabled for this feature to work. + + \sa viewportEntered(), activated(), clicked(), doubleClicked(), pressed() +*/ + +/*! + \fn void QAbstractItemView::viewportEntered() + + This signal is emitted when the mouse cursor enters the viewport. + Mouse tracking needs to be enabled for this feature to work. + + \sa entered() +*/ + +/*! + \fn void QAbstractItemView::pressed(const QModelIndex &index) + + This signal is emitted when a mouse button is pressed. The item + the mouse was pressed on is specified by \a index. The signal is + only emitted when the index is valid. + + Use the QApplication::mouseButtons() function to get the state + of the mouse buttons. + + \sa activated(), clicked(), doubleClicked(), entered() +*/ + +/*! + \fn void QAbstractItemView::clicked(const QModelIndex &index) + + This signal is emitted when a mouse button is clicked. The item + the mouse was clicked on is specified by \a index. The signal is + only emitted when the index is valid. + + \sa activated(), doubleClicked(), entered(), pressed() +*/ + +/*! + \fn void QAbstractItemView::doubleClicked(const QModelIndex &index) + + This signal is emitted when a mouse button is double-clicked. The + item the mouse was double-clicked on is specified by \a index. + The signal is only emitted when the index is valid. + + \sa clicked(), activated() +*/ + +/*! + \fn QModelIndex QAbstractItemView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) = 0 + + Returns a QModelIndex object pointing to the next object in the view, + based on the given \a cursorAction and keyboard modifiers specified + by \a modifiers. + + In the base class this is a pure virtual function. +*/ + +/*! + \fn int QAbstractItemView::horizontalOffset() const = 0 + + Returns the horizontal offset of the view. + + In the base class this is a pure virtual function. + + \sa verticalOffset() +*/ + +/*! + \fn int QAbstractItemView::verticalOffset() const = 0 + + Returns the vertical offset of the view. + + In the base class this is a pure virtual function. + + \sa horizontalOffset() +*/ + +/*! + \fn bool QAbstractItemView::isIndexHidden(const QModelIndex &index) const + + Returns true if the item referred to by the given \a index is hidden in the view, + otherwise returns false. + + Hiding is a view specific feature. For example in TableView a column can be marked + as hidden or a row in the TreeView. + + In the base class this is a pure virtual function. +*/ + +/*! + \fn void QAbstractItemView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags flags) + + Applies the selection \a flags to the items in or touched by the + rectangle, \a rect. + + When implementing your own itemview setSelection should call + selectionModel()->select(selection, flags) where selection + is either an empty QModelIndex or a QItemSelection that contains + all items that are contained in \a rect. + + \sa selectionCommand(), selectedIndexes() +*/ + +/*! + \fn QRegion QAbstractItemView::visualRegionForSelection(const QItemSelection &selection) const = 0 + + Returns the region from the viewport of the items in the given + \a selection. + + In the base class this is a pure virtual function. + + \sa visualRect(), selectedIndexes() +*/ + +/*! + \fn void QAbstractItemView::update() + \internal +*/ + +/*! + Constructs an abstract item view with the given \a parent. +*/ +QAbstractItemView::QAbstractItemView(QWidget *parent) + : QAbstractScrollArea(*(new QAbstractItemViewPrivate), parent) +{ + d_func()->init(); +} + +/*! + \internal +*/ +QAbstractItemView::QAbstractItemView(QAbstractItemViewPrivate &dd, QWidget *parent) + : QAbstractScrollArea(dd, parent) +{ + d_func()->init(); +} + +/*! + Destroys the view. +*/ +QAbstractItemView::~QAbstractItemView() +{ +} + +/*! + Sets the \a model for the view to present. + + This function will create and set a new selection model, replacing any + model that was previously set with setSelectionModel(). However, the old + selection model will not be deleted as it may be shared between several + views. We recommend that you delete the old selection model if it is no + longer required. This is done with the following code: + + \snippet doc/src/snippets/code/src_gui_itemviews_qabstractitemview.cpp 2 + + If both the old model and the old selection model do not have parents, or + if their parents are long-lived objects, it may be preferable to call their + deleteLater() functions to explicitly delete them. + + The view \e{does not} take ownership of the model unless it is the model's + parent object because the view may be shared between many different views. + + \sa selectionModel(), setSelectionModel() +*/ +void QAbstractItemView::setModel(QAbstractItemModel *model) +{ + Q_D(QAbstractItemView); + if (model == d->model) + return; + if (d->model && d->model != QAbstractItemModelPrivate::staticEmptyModel()) { + disconnect(d->model, SIGNAL(destroyed()), + this, SLOT(_q_modelDestroyed())); + disconnect(d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(dataChanged(QModelIndex,QModelIndex))); + disconnect(d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(rowsInserted(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(rowsAboutToBeRemoved(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_rowsRemoved(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_columnsAboutToBeRemoved(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(_q_columnsRemoved(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(_q_columnsInserted(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(modelReset()), this, SLOT(reset())); + disconnect(d->model, SIGNAL(layoutChanged()), this, SLOT(_q_layoutChanged())); + } + d->model = (model ? model : QAbstractItemModelPrivate::staticEmptyModel()); + + // These asserts do basic sanity checking of the model + Q_ASSERT_X(d->model->index(0,0) == d->model->index(0,0), + "QAbstractItemView::setModel", + "A model should return the exact same index " + "(including its internal id/pointer) when asked for it twice in a row."); + Q_ASSERT_X(d->model->index(0,0).parent() == QModelIndex(), + "QAbstractItemView::setModel", + "The parent of a top level index should be invalid"); + + if (d->model && d->model != QAbstractItemModelPrivate::staticEmptyModel()) { + connect(d->model, SIGNAL(destroyed()), + this, SLOT(_q_modelDestroyed())); + connect(d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(dataChanged(QModelIndex,QModelIndex))); + connect(d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(rowsInserted(QModelIndex,int,int))); + connect(d->model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(rowsAboutToBeRemoved(QModelIndex,int,int))); + connect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_rowsRemoved(QModelIndex,int,int))); + connect(d->model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_columnsAboutToBeRemoved(QModelIndex,int,int))); + connect(d->model, SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(_q_columnsRemoved(QModelIndex,int,int))); + connect(d->model, SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(_q_columnsInserted(QModelIndex,int,int))); + + connect(d->model, SIGNAL(modelReset()), this, SLOT(reset())); + connect(d->model, SIGNAL(layoutChanged()), this, SLOT(_q_layoutChanged())); + } + setSelectionModel(new QItemSelectionModel(d->model, this)); + reset(); // kill editors, set new root and do layout +} + +/*! + Returns the model that this view is presenting. +*/ +QAbstractItemModel *QAbstractItemView::model() const +{ + Q_D(const QAbstractItemView); + return (d->model == QAbstractItemModelPrivate::staticEmptyModel() ? 0 : d->model); +} + +/*! + Sets the current selection model to the given \a selectionModel. + + Note that, if you call setModel() after this function, the given \a selectionModel + will be replaced by one created by the view. + + \note It is up to the application to delete the old selection model if it is no + longer needed; i.e., if it is not being used by other views. This will happen + automatically when its parent object is deleted. However, if it does not have a + parent, or if the parent is a long-lived object, it may be preferable to call its + deleteLater() function to explicitly delete it. + + \sa selectionModel(), setModel(), clearSelection() +*/ +void QAbstractItemView::setSelectionModel(QItemSelectionModel *selectionModel) +{ + // ### if the given model is null, we should use the original selection model + Q_ASSERT(selectionModel); + Q_D(QAbstractItemView); + + if (selectionModel->model() != d->model) { + qWarning("QAbstractItemView::setSelectionModel() failed: " + "Trying to set a selection model, which works on " + "a different model than the view."); + return; + } + + if (d->selectionModel) { + disconnect(d->selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + this, SLOT(selectionChanged(QItemSelection,QItemSelection))); + disconnect(d->selectionModel, SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(currentChanged(QModelIndex,QModelIndex))); + } + + d->selectionModel = selectionModel; + + if (d->selectionModel) { + connect(d->selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + this, SLOT(selectionChanged(QItemSelection,QItemSelection))); + connect(d->selectionModel, SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(currentChanged(QModelIndex,QModelIndex))); + } +} + +/*! + Returns the current selection model. + + \sa setSelectionModel(), selectedIndexes() +*/ +QItemSelectionModel* QAbstractItemView::selectionModel() const +{ + Q_D(const QAbstractItemView); + return d->selectionModel; +} + +/*! + Sets the item delegate for this view and its model to \a delegate. + This is useful if you want complete control over the editing and + display of items. + + Any existing delegate will be removed, but not deleted. QAbstractItemView + does not take ownership of \a delegate. + + \warning You should not share the same instance of a delegate between views. + Doing so can cause incorrect or unintuitive editing behavior since each + view connected to a given delegate may receive the \l{QAbstractItemDelegate::}{closeEditor()} + signal, and attempt to access, modify or close an editor that has already been closed. + + \sa itemDelegate() +*/ +void QAbstractItemView::setItemDelegate(QAbstractItemDelegate *delegate) +{ + Q_D(QAbstractItemView); + if (delegate == d->itemDelegate) + return; + + if (d->itemDelegate) { + if (d->delegateRefCount(d->itemDelegate) == 1) { + disconnect(d->itemDelegate, SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)), + this, SLOT(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint))); + disconnect(d->itemDelegate, SIGNAL(commitData(QWidget*)), this, SLOT(commitData(QWidget*))); + disconnect(d->itemDelegate, SIGNAL(sizeHintChanged(QModelIndex)), this, SLOT(doItemsLayout())); + } + } + + + if (delegate) { + if (d->delegateRefCount(delegate) == 0) { + connect(delegate, SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)), + this, SLOT(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint))); + connect(delegate, SIGNAL(commitData(QWidget*)), this, SLOT(commitData(QWidget*))); + qRegisterMetaType<QModelIndex>("QModelIndex"); + connect(delegate, SIGNAL(sizeHintChanged(QModelIndex)), this, SLOT(doItemsLayout()), Qt::QueuedConnection); + } + } + d->itemDelegate = delegate; +} + +/*! + Returns the item delegate used by this view and model. This is + either one set with setItemDelegate(), or the default one. + + \sa setItemDelegate() +*/ +QAbstractItemDelegate *QAbstractItemView::itemDelegate() const +{ + return d_func()->itemDelegate; +} + +/*! + \reimp +*/ +QVariant QAbstractItemView::inputMethodQuery(Qt::InputMethodQuery query) const +{ + const QModelIndex current = currentIndex(); + if (!current.isValid() || query != Qt::ImMicroFocus) + return QAbstractScrollArea::inputMethodQuery(query); + return visualRect(current); +} + +/*! + \since 4.2 + + Sets the given item \a delegate used by this view and model for the given + \a row. All items on \a row will be drawn and managed by \a delegate + instead of using the default delegate (i.e., itemDelegate()). + + Any existing row delegate for \a row will be removed, but not + deleted. QAbstractItemView does not take ownership of \a delegate. + + \note If a delegate has been assigned to both a row and a column, the row + delegate (i.e., this delegate) will take presedence and manage the + intersecting cell index. + + \warning You should not share the same instance of a delegate between views. + Doing so can cause incorrect or unintuitive editing behavior since each + view connected to a given delegate may receive the \l{QAbstractItemDelegate::}{closeEditor()} + signal, and attempt to access, modify or close an editor that has already been closed. + + \sa itemDelegateForRow(), setItemDelegateForColumn(), itemDelegate() +*/ +void QAbstractItemView::setItemDelegateForRow(int row, QAbstractItemDelegate *delegate) +{ + Q_D(QAbstractItemView); + if (QAbstractItemDelegate *rowDelegate = d->rowDelegates.value(row, 0)) { + if (d->delegateRefCount(rowDelegate) == 1) { + disconnect(rowDelegate, SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)), + this, SLOT(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint))); + disconnect(rowDelegate, SIGNAL(commitData(QWidget*)), this, SLOT(commitData(QWidget*))); + } + d->rowDelegates.remove(row); + } + if (delegate) { + if (d->delegateRefCount(delegate) == 0) { + connect(delegate, SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)), + this, SLOT(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint))); + connect(delegate, SIGNAL(commitData(QWidget*)), this, SLOT(commitData(QWidget*))); + } + d->rowDelegates.insert(row, delegate); + } +} + +/*! + \since 4.2 + + Returns the item delegate used by this view and model for the given \a row, + or 0 if no delegate has been assigned. You can call itemDelegate() to get a + pointer to the current delegate for a given index. + + \sa setItemDelegateForRow(), itemDelegateForColumn(), setItemDelegate() +*/ +QAbstractItemDelegate *QAbstractItemView::itemDelegateForRow(int row) const +{ + Q_D(const QAbstractItemView); + return d->rowDelegates.value(row, 0); +} + +/*! + \since 4.2 + + Sets the given item \a delegate used by this view and model for the given + \a column. All items on \a column will be drawn and managed by \a delegate + instead of using the default delegate (i.e., itemDelegate()). + + Any existing column delegate for \a column will be removed, but not + deleted. QAbstractItemView does not take ownership of \a delegate. + + \note If a delegate has been assigned to both a row and a column, the row + delegate will take presedence and manage the intersecting cell index. + + \warning You should not share the same instance of a delegate between views. + Doing so can cause incorrect or unintuitive editing behavior since each + view connected to a given delegate may receive the \l{QAbstractItemDelegate::}{closeEditor()} + signal, and attempt to access, modify or close an editor that has already been closed. + + \sa itemDelegateForColumn(), setItemDelegateForRow(), itemDelegate() +*/ +void QAbstractItemView::setItemDelegateForColumn(int column, QAbstractItemDelegate *delegate) +{ + Q_D(QAbstractItemView); + if (QAbstractItemDelegate *columnDelegate = d->columnDelegates.value(column, 0)) { + if (d->delegateRefCount(columnDelegate) == 1) { + disconnect(columnDelegate, SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)), + this, SLOT(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint))); + disconnect(columnDelegate, SIGNAL(commitData(QWidget*)), this, SLOT(commitData(QWidget*))); + } + d->columnDelegates.remove(column); + } + if (delegate) { + if (d->delegateRefCount(delegate) == 0) { + connect(delegate, SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)), + this, SLOT(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint))); + connect(delegate, SIGNAL(commitData(QWidget*)), this, SLOT(commitData(QWidget*))); + } + d->columnDelegates.insert(column, delegate); + } +} + +/*! + \since 4.2 + + Returns the item delegate used by this view and model for the given \a + column. You can call itemDelegate() to get a pointer to the current delegate + for a given index. + + \sa setItemDelegateForColumn(), itemDelegateForRow(), itemDelegate() +*/ +QAbstractItemDelegate *QAbstractItemView::itemDelegateForColumn(int column) const +{ + Q_D(const QAbstractItemView); + return d->columnDelegates.value(column, 0); +} + +/*! + Returns the item delegate used by this view and model for + the given \a index. +*/ +QAbstractItemDelegate *QAbstractItemView::itemDelegate(const QModelIndex &index) const +{ + Q_D(const QAbstractItemView); + return d->delegateForIndex(index); +} + +/*! + \property QAbstractItemView::selectionMode + \brief which selection mode the view operates in + + This property controls whether the user can select one or many items + and, in many-item selections, whether the selection must be a + continuous range of items. + + \sa SelectionMode SelectionBehavior +*/ +void QAbstractItemView::setSelectionMode(SelectionMode mode) +{ + Q_D(QAbstractItemView); + d->selectionMode = mode; +} + +QAbstractItemView::SelectionMode QAbstractItemView::selectionMode() const +{ + Q_D(const QAbstractItemView); + return d->selectionMode; +} + +/*! + \property QAbstractItemView::selectionBehavior + \brief which selection behavior the view uses + + This property holds whether selections are done + in terms of single items, rows or columns. + + \sa SelectionMode SelectionBehavior +*/ + +void QAbstractItemView::setSelectionBehavior(QAbstractItemView::SelectionBehavior behavior) +{ + Q_D(QAbstractItemView); + d->selectionBehavior = behavior; +} + +QAbstractItemView::SelectionBehavior QAbstractItemView::selectionBehavior() const +{ + Q_D(const QAbstractItemView); + return d->selectionBehavior; +} + +/*! + Sets the current item to be the item at \a index. + Depending on the current selection mode, the item may also be selected. + Note that this function also updates the starting position for any + new selections the user performs. + + To set an item as the current item without selecting it, call + + \c{selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate);} + + \sa currentIndex(), currentChanged(), selectionMode +*/ +void QAbstractItemView::setCurrentIndex(const QModelIndex &index) +{ + Q_D(QAbstractItemView); + if (d->selectionModel && (!index.isValid() || d->isIndexEnabled(index))) { + QItemSelectionModel::SelectionFlags command = selectionCommand(index, 0); + d->selectionModel->setCurrentIndex(index, command); + d->currentIndexSet = true; + if ((command & QItemSelectionModel::Current) == 0) + d->pressedPosition = visualRect(currentIndex()).center() + d->offset(); + } +} + +/*! + Returns the model index of the current item. + + \sa setCurrentIndex() +*/ +QModelIndex QAbstractItemView::currentIndex() const +{ + Q_D(const QAbstractItemView); + return d->selectionModel ? d->selectionModel->currentIndex() : QModelIndex(); +} + + +/*! + Reset the internal state of the view. + + \warning This function will reset open editors, scroll bar positions, + selections, etc. Existing changes will not be committed. If you would like + to save your changes when resetting the view, you can reimplement this + function, commit your changes, and then call the superclass' + implementation. +*/ +void QAbstractItemView::reset() +{ + Q_D(QAbstractItemView); + QList<QEditorInfo>::const_iterator it = d->editors.constBegin(); + for (; it != d->editors.constEnd(); ++it) + d->releaseEditor(it->editor); + d->editors.clear(); + d->persistent.clear(); + d->currentIndexSet = false; + setState(NoState); + setRootIndex(QModelIndex()); +} + +/*! + Sets the root item to the item at the given \a index. + + \sa rootIndex() +*/ +void QAbstractItemView::setRootIndex(const QModelIndex &index) +{ + Q_D(QAbstractItemView); + if (index.isValid() && index.model() != d->model) { + qWarning("QAbstractItemView::setRootIndex failed : index must be from the currently set model"); + return; + } + d->root = index; + d->doDelayedItemsLayout(); +} + +/*! + Returns the model index of the model's root item. The root item is + the parent item to the view's toplevel items. The root can be invalid. + + \sa setRootIndex() +*/ +QModelIndex QAbstractItemView::rootIndex() const +{ + return QModelIndex(d_func()->root); +} + +/*! + Selects all item in the view. + This function wil use the selection selection behavior + set on the view when selecting. + + \sa setSelection(), selectedIndexes(), clearSelection() +*/ +void QAbstractItemView::selectAll() +{ + Q_D(QAbstractItemView); + SelectionMode mode = d->selectionMode; + if (mode == MultiSelection || mode == ExtendedSelection) + d->selectAll(QItemSelectionModel::ClearAndSelect + |d->selectionBehaviorFlags()); + else if (mode != SingleSelection) + d->selectAll(selectionCommand(d->model->index(0, 0, d->root))); +} + +/*! + Starts editing the item corresponding to the given \a index if it is + editable. + + Note that this function does not change the current index. Since the current + index defines the next and previous items to edit, users may find that + keyboard navigation does not work as expected. To provide consistent navigation + behavior, call setCurrentIndex() before this function with the same model + index. + + \sa QModelIndex::flags() +*/ +void QAbstractItemView::edit(const QModelIndex &index) +{ + Q_D(QAbstractItemView); + if (!d->isIndexValid(index)) + qWarning("edit: index was invalid"); + if (!edit(index, AllEditTriggers, 0)) + qWarning("edit: editing failed"); +} + +/*! + Deselects all selected items. The current index will not be changed. + + \sa setSelection(), selectAll() +*/ +void QAbstractItemView::clearSelection() +{ + Q_D(QAbstractItemView); + if (d->selectionModel) + d->selectionModel->clearSelection(); +} + +/*! + \internal + + This function is intended to lay out the items in the view. + The default implementation just calls updateGeometries() and updates the viewport. +*/ +void QAbstractItemView::doItemsLayout() +{ + Q_D(QAbstractItemView); + d->interruptDelayedItemsLayout(); + updateGeometries(); + d->viewport->update(); +} + +/*! + \property QAbstractItemView::editTriggers + \brief which actions will initiate item editing + + This property is a selection of flags defined by + \l{EditTrigger}, combined using the OR + operator. The view will only initiate the editing of an item if the + action performed is set in this property. +*/ +void QAbstractItemView::setEditTriggers(EditTriggers actions) +{ + Q_D(QAbstractItemView); + d->editTriggers = actions; +} + +QAbstractItemView::EditTriggers QAbstractItemView::editTriggers() const +{ + Q_D(const QAbstractItemView); + return d->editTriggers; +} + +/*! + \since 4.2 + \property QAbstractItemView::verticalScrollMode + \brief how the view scrolls its contents in the vertical direction + + This property controlls how the view scroll its contents vertically. + Scrolling can be done either per pixel or per item. +*/ + +void QAbstractItemView::setVerticalScrollMode(ScrollMode mode) +{ + Q_D(QAbstractItemView); + if (mode == d->verticalScrollMode) + return; + QModelIndex topLeft = indexAt(QPoint(0, 0)); + d->verticalScrollMode = mode; + updateGeometries(); // update the scroll bars + scrollTo(topLeft, QAbstractItemView::PositionAtTop); +} + +QAbstractItemView::ScrollMode QAbstractItemView::verticalScrollMode() const +{ + Q_D(const QAbstractItemView); + return d->verticalScrollMode; +} + +/*! + \since 4.2 + \property QAbstractItemView::horizontalScrollMode + \brief how the view scrolls its contents in the horizontal direction + + This property controlls how the view scroll its contents horizontally. + Scrolling can be done either per pixel or per item. +*/ + +void QAbstractItemView::setHorizontalScrollMode(ScrollMode mode) +{ + Q_D(QAbstractItemView); + d->horizontalScrollMode = mode; + updateGeometries(); // update the scroll bars +} + +QAbstractItemView::ScrollMode QAbstractItemView::horizontalScrollMode() const +{ + Q_D(const QAbstractItemView); + return d->horizontalScrollMode; +} + +#ifndef QT_NO_DRAGANDDROP +/*! + \since 4.2 + \property QAbstractItemView::dragDropOverwriteMode + \brief the view's drag and drop behavior + + If its value is \c true, the selected data will overwrite the + existing item data when dropped, while moving the data will clear + the item. If its value is \c false, the selected data will be + inserted as a new item when the data is dropped. When the data is + moved, the item is removed as well. + + The default value is \c false, as in the QListView and QTreeView + subclasses. In the QTableView subclass, on the other hand, the + property has been set to \c true. + + Note: This is not intended to prevent overwriting of items. + The model's implementation of flags() should do that by not + returning Qt::ItemIsDropEnabled. + + \sa dragDropMode +*/ +void QAbstractItemView::setDragDropOverwriteMode(bool overwrite) +{ + Q_D(QAbstractItemView); + d->overwrite = overwrite; +} + +bool QAbstractItemView::dragDropOverwriteMode() const +{ + Q_D(const QAbstractItemView); + return d->overwrite; +} +#endif + +/*! + \property QAbstractItemView::autoScroll + \brief whether autoscrolling in drag move events is enabled + + If this property is set to true (the default), the + QAbstractItemView automatically scrolls the contents of the view + if the user drags within 16 pixels of the viewport edge. If the current + item changes, then the view will scroll automatically to ensure that the + current item is fully visible. + + This property only works if the viewport accepts drops. Autoscroll is + switched off by setting this property to false. +*/ + +void QAbstractItemView::setAutoScroll(bool enable) +{ + Q_D(QAbstractItemView); + d->autoScroll = enable; +} + +bool QAbstractItemView::hasAutoScroll() const +{ + Q_D(const QAbstractItemView); + return d->autoScroll; +} + +/*! + \since 4.4 + \property QAbstractItemView::autoScrollMargin + \brief the size of the area when auto scrolling is triggered + + This property controlls the size of the area at the edge of the viewport that + triggers autoscrolling. The default value is 16 pixels. +*/ +void QAbstractItemView::setAutoScrollMargin(int margin) +{ + Q_D(QAbstractItemView); + d->autoScrollMargin = margin; +} + +int QAbstractItemView::autoScrollMargin() const +{ + Q_D(const QAbstractItemView); + return d->autoScrollMargin; +} + +/*! + \property QAbstractItemView::tabKeyNavigation + \brief whether item navigation with tab and backtab is enabled. +*/ + +void QAbstractItemView::setTabKeyNavigation(bool enable) +{ + Q_D(QAbstractItemView); + d->tabKeyNavigation = enable; +} + +bool QAbstractItemView::tabKeyNavigation() const +{ + Q_D(const QAbstractItemView); + return d->tabKeyNavigation; +} + +#ifndef QT_NO_DRAGANDDROP +/*! + \property QAbstractItemView::showDropIndicator + \brief whether the drop indicator is shown when dragging items and dropping. + + \sa dragEnabled DragDropMode dragDropOverwriteMode acceptDrops +*/ + +void QAbstractItemView::setDropIndicatorShown(bool enable) +{ + Q_D(QAbstractItemView); + d->showDropIndicator = enable; +} + +bool QAbstractItemView::showDropIndicator() const +{ + Q_D(const QAbstractItemView); + return d->showDropIndicator; +} + +/*! + \property QAbstractItemView::dragEnabled + \brief whether the view supports dragging of its own items + + \sa showDropIndicator DragDropMode dragDropOverwriteMode acceptDrops +*/ + +void QAbstractItemView::setDragEnabled(bool enable) +{ + Q_D(QAbstractItemView); + d->dragEnabled = enable; +} + +bool QAbstractItemView::dragEnabled() const +{ + Q_D(const QAbstractItemView); + return d->dragEnabled; +} + +/*! + \since 4.2 + \enum QAbstractItemView::DragDropMode + + Describes the various drag and drop events the view can act upon. + By default the view does not support dragging or dropping (\c + NoDragDrop). + + \value NoDragDrop Does not support dragging or dropping. + \value DragOnly The view supports dragging of its own items + \value DropOnly The view accepts drops + \value DragDrop The view supports both dragging and dropping + \value InternalMove The view accepts move (\bold{not copy}) operations only + from itself. + + Note that the model used needs to provide support for drag and drop operations. + + \sa setDragDropMode() {Using Drag and Drop with Item Views} +*/ + +/*! + \property QAbstractItemView::dragDropMode + \brief the drag and drop event the view will act upon + + \since 4.2 + \sa showDropIndicator dragDropOverwriteMode +*/ +void QAbstractItemView::setDragDropMode(DragDropMode behavior) +{ + Q_D(QAbstractItemView); + d->dragDropMode = behavior; + setDragEnabled(behavior == DragOnly || behavior == DragDrop || behavior == InternalMove); + setAcceptDrops(behavior == DropOnly || behavior == DragDrop || behavior == InternalMove); +} + +QAbstractItemView::DragDropMode QAbstractItemView::dragDropMode() const +{ + Q_D(const QAbstractItemView); + DragDropMode setBehavior = d->dragDropMode; + if (!dragEnabled() && !acceptDrops()) + return NoDragDrop; + + if (dragEnabled() && !acceptDrops()) + return DragOnly; + + if (!dragEnabled() && acceptDrops()) + return DropOnly; + + if (dragEnabled() && acceptDrops()) { + if (setBehavior == InternalMove) + return setBehavior; + else + return DragDrop; + } + + return NoDragDrop; +} + +#endif // QT_NO_DRAGANDDROP + +/*! + \property QAbstractItemView::alternatingRowColors + \brief whether to draw the background using alternating colors + + If this property is true, the item background will be drawn using + QPalette::Base and QPalette::AlternateBase; otherwise the background + will be drawn using the QPalette::Base color. + + By default, this property is false. +*/ +void QAbstractItemView::setAlternatingRowColors(bool enable) +{ + Q_D(QAbstractItemView); + d->alternatingColors = enable; + if (isVisible()) + d->viewport->update(); +} + +bool QAbstractItemView::alternatingRowColors() const +{ + Q_D(const QAbstractItemView); + return d->alternatingColors; +} + +/*! + \property QAbstractItemView::iconSize + \brief the size of items' icons + + Setting this property when the view is visible will cause the + items to be laid out again. +*/ +void QAbstractItemView::setIconSize(const QSize &size) +{ + Q_D(QAbstractItemView); + if (size == d->iconSize) + return; + d->iconSize = size; + d->doDelayedItemsLayout(); +} + +QSize QAbstractItemView::iconSize() const +{ + Q_D(const QAbstractItemView); + return d->iconSize; +} + +/*! + \property QAbstractItemView::textElideMode + + \brief the the position of the "..." in elided text. + + The default value for all item views is Qt::ElideRight. +*/ +void QAbstractItemView::setTextElideMode(Qt::TextElideMode mode) +{ + Q_D(QAbstractItemView); + d->textElideMode = mode; +} + +Qt::TextElideMode QAbstractItemView::textElideMode() const +{ + return d_func()->textElideMode; +} + +/*! + \reimp +*/ +bool QAbstractItemView::focusNextPrevChild(bool next) +{ + Q_D(QAbstractItemView); + if (d->tabKeyNavigation && isEnabled() && d->viewport->isEnabled()) { + QKeyEvent event(QEvent::KeyPress, next ? Qt::Key_Tab : Qt::Key_Backtab, Qt::NoModifier); + keyPressEvent(&event); + if (event.isAccepted()) + return true; + } + return QAbstractScrollArea::focusNextPrevChild(next); +} + +/*! + \reimp +*/ +bool QAbstractItemView::event(QEvent *event) +{ + Q_D(QAbstractItemView); + switch (event->type()) { + case QEvent::Show: + { + d->executePostedLayout(); //make sure we set the layout properly + const QModelIndex current = currentIndex(); + if (current.isValid() && (d->state == QAbstractItemView::EditingState || d->autoScroll)) + scrollTo(current); + } + break; + case QEvent::LocaleChange: + viewport()->update(); + break; + case QEvent::LayoutDirectionChange: + case QEvent::ApplicationLayoutDirectionChange: + updateGeometries(); + break; + case QEvent::StyleChange: + doItemsLayout(); + break; + case QEvent::FocusOut: + d->checkPersistentEditorFocus(); + break; + default: + break; + } + return QAbstractScrollArea::event(event); +} + +/*! + \fn bool QAbstractItemView::viewportEvent(QEvent *event) + + This function is used to handle tool tips, and What's + This? mode, if the given \a event is a QEvent::ToolTip,or a + QEvent::WhatsThis. It passes all other + events on to its base class viewportEvent() handler. +*/ +bool QAbstractItemView::viewportEvent(QEvent *event) +{ + Q_D(QAbstractItemView); + switch (event->type()) { + case QEvent::HoverEnter: { + QHoverEvent *he = static_cast<QHoverEvent*>(event); + d->hover = indexAt(he->pos()); + d->viewport->update(visualRect(d->hover)); + break; } + case QEvent::HoverLeave: { + d->viewport->update(visualRect(d->hover)); // update old + d->hover = QModelIndex(); + break; } + case QEvent::HoverMove: { + QHoverEvent *he = static_cast<QHoverEvent*>(event); + QModelIndex old = d->hover; + d->hover = indexAt(he->pos()); + if (d->hover != old) + d->viewport->update(visualRect(old)|visualRect(d->hover)); + break; } + case QEvent::Enter: + d->viewportEnteredNeeded = true; + break; + case QEvent::Leave: + d->enteredIndex = QModelIndex(); + break; + case QEvent::ToolTip: + case QEvent::QueryWhatsThis: + case QEvent::WhatsThis: { + QHelpEvent *he = static_cast<QHelpEvent*>(event); + const QModelIndex index = indexAt(he->pos()); + QStyleOptionViewItemV4 option = d->viewOptionsV4(); + option.rect = visualRect(index); + option.state |= (index == currentIndex() ? QStyle::State_HasFocus : QStyle::State_None); + bool retval = false; + // ### Qt 5: make this a normal function call to a virtual function + QMetaObject::invokeMethod(d->delegateForIndex(index), "helpEvent", + Q_RETURN_ARG(bool, retval), + Q_ARG(QHelpEvent *, he), + Q_ARG(QAbstractItemView *, this), + Q_ARG(QStyleOptionViewItem, option), + Q_ARG(QModelIndex, index)); + return retval; + } + case QEvent::FontChange: + d->doDelayedItemsLayout(); // the size of the items will change + break; + case QEvent::WindowActivate: + case QEvent::WindowDeactivate: + d->viewport->update(); + break; + default: + break; + } + return QAbstractScrollArea::viewportEvent(event); +} + +/*! + This function is called with the given \a event when a mouse button is pressed + while the cursor is inside the widget. If a valid item is pressed on it is made + into the current item. This function emits the pressed() signal. +*/ +void QAbstractItemView::mousePressEvent(QMouseEvent *event) +{ + Q_D(QAbstractItemView); + d->delayedAutoScroll.stop(); //any interaction with the view cancel the auto scrolling + QPoint pos = event->pos(); + QPersistentModelIndex index = indexAt(pos); + + if (!d->selectionModel + || (d->state == EditingState && d->hasEditor(index))) + return; + + d->pressedAlreadySelected = d->selectionModel->isSelected(index); + d->pressedIndex = index; + d->pressedModifiers = event->modifiers(); + QItemSelectionModel::SelectionFlags command = selectionCommand(index, event); + QPoint offset = d->offset(); + if ((command & QItemSelectionModel::Current) == 0) + d->pressedPosition = pos + offset; + + if (d->pressedPosition == QPoint(-1, -1)) + d->pressedPosition = visualRect(currentIndex()).center() + offset; + + if (edit(index, NoEditTriggers, event)) + return; + + if (index.isValid() && d->isIndexEnabled(index)) { + // we disable scrollTo for mouse press so the item doesn't change position + // when the user is interacting with it (ie. clicking on it) + bool autoScroll = d->autoScroll; + d->autoScroll = false; + // setSelection will update the current item too + // and it seems that the two updates are not merged + bool updates = d->viewport->updatesEnabled(); + d->viewport->setUpdatesEnabled(command == QItemSelectionModel::NoUpdate); + d->selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + d->viewport->setUpdatesEnabled(updates); + d->autoScroll = autoScroll; + QRect rect(d->pressedPosition - offset, pos); + setSelection(rect, command); + + // signal handlers may change the model + emit pressed(index); + if (d->autoScroll) { + //we delay the autoscrolling to filter out double click event + //100 is to be sure that there won't be a double-click misinterpreted as a 2 single clicks + d->delayedAutoScroll.start(QApplication::doubleClickInterval()+100, this); + } + + } else { + // Forces a finalize() even if mouse is pressed, but not on a item + d->selectionModel->select(QModelIndex(), QItemSelectionModel::Select); + } +} + +/*! + This function is called with the given \a event when a mouse move event is + sent to the widget. If a selection is in progress and new items are moved + over the selection is extended; if a drag is in progress it is continued. +*/ +void QAbstractItemView::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QAbstractItemView); + QPoint topLeft; + QPoint bottomRight = event->pos(); + + if (state() == ExpandingState || state() == CollapsingState) + return; + +#ifndef QT_NO_DRAGANDDROP + if (state() == DraggingState) { + topLeft = d->pressedPosition - d->offset(); + if ((topLeft - bottomRight).manhattanLength() > QApplication::startDragDistance()) { + d->pressedIndex = QModelIndex(); + startDrag(d->model->supportedDragActions()); + setState(NoState); // the startDrag will return when the dnd operation is done + stopAutoScroll(); + } + return; + } +#endif // QT_NO_DRAGANDDROP + + QModelIndex index = indexAt(bottomRight); + QModelIndex buddy = d->model->buddy(d->pressedIndex); + if ((state() == EditingState && d->hasEditor(buddy)) + || edit(index, NoEditTriggers, event)) + return; + + if (d->selectionMode != SingleSelection) + topLeft = d->pressedPosition - d->offset(); + else + topLeft = bottomRight; + + if (d->viewportEnteredNeeded || d->enteredIndex != index) { + d->viewportEnteredNeeded = false; + + // signal handlers may change the model + QPersistentModelIndex persistent = index; + if (persistent.isValid()) { + emit entered(persistent); +#ifndef QT_NO_STATUSTIP + QString statustip = d->model->data(persistent, Qt::StatusTipRole).toString(); + if (parent() && !statustip.isEmpty()) { + QStatusTipEvent tip(statustip); + QApplication::sendEvent(parent(), &tip); + } +#endif + } else { +#ifndef QT_NO_STATUSTIP + if (parent()) { + QString emptyString; + QStatusTipEvent tip(emptyString); + QApplication::sendEvent(parent(), &tip); + } +#endif + emit viewportEntered(); + } + d->enteredIndex = persistent; + index = persistent; + } + +#ifndef QT_NO_DRAGANDDROP + if (index.isValid() + && d->dragEnabled + && (state() != DragSelectingState) + && (event->buttons() != Qt::NoButton) + && !d->selectedDraggableIndexes().isEmpty()) { + setState(DraggingState); + return; + } +#endif + + if ((event->buttons() & Qt::LeftButton) && d->selectionAllowed(index) && d->selectionModel) { + setState(DragSelectingState); + QItemSelectionModel::SelectionFlags command = selectionCommand(index, event); + + // Do the normalize ourselves, since QRect::normalized() is flawed + QRect selectionRect = QRect(topLeft, bottomRight); + QPersistentModelIndex persistent = index; + setSelection(selectionRect, command); + + // set at the end because it might scroll the view + if (persistent.isValid() + && (persistent != d->selectionModel->currentIndex()) + && d->isIndexEnabled(persistent)) + d->selectionModel->setCurrentIndex(persistent, QItemSelectionModel::NoUpdate); + } +} + +/*! + This function is called with the given \a event when a mouse button is released, + after a mouse press event on the widget. If a user presses the mouse inside your + widget and then drags the mouse to another location before releasing the mouse button, + your widget receives the release event. The function will emit the clicked() signal if an + item was being pressed. +*/ +void QAbstractItemView::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(QAbstractItemView); + + QPoint pos = event->pos(); + QPersistentModelIndex index = indexAt(pos); + + if (state() == EditingState) { + if (d->isIndexValid(index) + && d->isIndexEnabled(index) + && d->sendDelegateEvent(index, event)) + d->viewport->update(visualRect(index)); + return; + } + + bool click = (index == d->pressedIndex && index.isValid()); + bool selectedClicked = click && (event->button() & Qt::LeftButton) && d->pressedAlreadySelected; + EditTrigger trigger = (selectedClicked ? SelectedClicked : NoEditTriggers); + bool edited = edit(index, trigger, event); + + if (d->selectionModel) + d->selectionModel->select(index, selectionCommand(index, event)); + + setState(NoState); + + if (click) { + emit clicked(index); + if (edited) + return; + if (style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, this)) + emit activated(index); + } +} + +/*! + This function is called with the given \a event when a mouse button is + double clicked inside the widget. If the double-click is on a valid item it + emits the doubleClicked() signal and calls edit() on the item. +*/ +void QAbstractItemView::mouseDoubleClickEvent(QMouseEvent *event) +{ + Q_D(QAbstractItemView); + + QModelIndex index = indexAt(event->pos()); + if (!index.isValid() + || !d->isIndexEnabled(index) + || (d->pressedIndex != index)) { + QMouseEvent me(QEvent::MouseButtonPress, + event->pos(), event->button(), + event->buttons(), event->modifiers()); + mousePressEvent(&me); + return; + } + // signal handlers may change the model + QPersistentModelIndex persistent = index; + emit doubleClicked(persistent); + if ((event->button() & Qt::LeftButton) && !edit(persistent, DoubleClicked, event) + && !style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, this)) + emit activated(persistent); +} + +#ifndef QT_NO_DRAGANDDROP + +/*! + This function is called with the given \a event when a drag and drop operation enters + the widget. If the drag is over a valid dropping place (e.g. over an item that + accepts drops), the event is accepted; otherwise it is ignored. + + \sa dropEvent() startDrag() +*/ +void QAbstractItemView::dragEnterEvent(QDragEnterEvent *event) +{ + if (dragDropMode() == InternalMove + && (event->source() != this|| !(event->possibleActions() & Qt::MoveAction))) + return; + + if (d_func()->canDecode(event)) { + event->accept(); + setState(DraggingState); + } else { + event->ignore(); + } +} + +/*! + This function is called continuously with the given \a event during a drag and + drop operation over the widget. It can cause the view to scroll if, for example, + the user drags a selection to view's right or bottom edge. In this case, the + event will be accepted; otherwise it will be ignored. + + \sa dropEvent() startDrag() +*/ +void QAbstractItemView::dragMoveEvent(QDragMoveEvent *event) +{ + Q_D(QAbstractItemView); + if (dragDropMode() == InternalMove + && (event->source() != this || !(event->possibleActions() & Qt::MoveAction))) + return; + + // ignore by default + event->ignore(); + + QModelIndex index = indexAt(event->pos()); + d->hover = index; + if (!d->droppingOnItself(event, index) + && d->canDecode(event)) { + + if (index.isValid() && d->showDropIndicator) { + QRect rect = visualRect(index); + d->dropIndicatorPosition = d->position(event->pos(), rect, index); + switch (d->dropIndicatorPosition) { + case AboveItem: + if (d->isIndexDropEnabled(index.parent())) { + d->dropIndicatorRect = QRect(rect.left(), rect.top(), rect.width(), 0); + event->accept(); + } else { + d->dropIndicatorRect = QRect(); + } + break; + case BelowItem: + if (d->isIndexDropEnabled(index.parent())) { + d->dropIndicatorRect = QRect(rect.left(), rect.bottom(), rect.width(), 0); + event->accept(); + } else { + d->dropIndicatorRect = QRect(); + } + break; + case OnItem: + if (d->isIndexDropEnabled(index)) { + d->dropIndicatorRect = rect; + event->accept(); + } else { + d->dropIndicatorRect = QRect(); + } + break; + case OnViewport: + d->dropIndicatorRect = QRect(); + if (d->isIndexDropEnabled(rootIndex())) { + event->accept(); // allow dropping in empty areas + } + break; + } + } else { + d->dropIndicatorRect = QRect(); + d->dropIndicatorPosition = OnViewport; + if (d->isIndexDropEnabled(rootIndex())) { + event->accept(); // allow dropping in empty areas + } + } + d->viewport->update(); + } // can decode + + if (d->shouldAutoScroll(event->pos())) + startAutoScroll(); +} + +/*! + \internal + Return true if this is a move from ourself and \a index is a child of the selection that + is being moved. + */ +bool QAbstractItemViewPrivate::droppingOnItself(QDropEvent *event, const QModelIndex &index) +{ + Q_Q(QAbstractItemView); + Qt::DropAction dropAction = event->dropAction(); + if (q->dragDropMode() == QAbstractItemView::InternalMove) + dropAction = Qt::MoveAction; + if (event->source() == q + && event->possibleActions() & Qt::MoveAction + && dropAction == Qt::MoveAction) { + QModelIndexList selectedIndexes = q->selectedIndexes(); + QModelIndex child = index; + while (child.isValid() && child != root) { + if (selectedIndexes.contains(child)) + return true; + child = child.parent(); + } + } + return false; +} + +/*! + \fn void QAbstractItemView::dragLeaveEvent(QDragLeaveEvent *event) + + This function is called when the item being dragged leaves the view. + The \a event describes the state of the drag and drop operation. +*/ +void QAbstractItemView::dragLeaveEvent(QDragLeaveEvent *) +{ + Q_D(QAbstractItemView); + stopAutoScroll(); + setState(NoState); + d->hover = QModelIndex(); + d->viewport->update(); +} + +/*! + This function is called with the given \a event when a drop event occurs over + the widget. If the model accepts the even position the drop event is accepted; + otherwise it is ignored. + + \sa startDrag() +*/ +void QAbstractItemView::dropEvent(QDropEvent *event) +{ + Q_D(QAbstractItemView); + if (dragDropMode() == InternalMove) { + if (event->source() != this || !(event->possibleActions() & Qt::MoveAction)) + return; + } + + QModelIndex index; + int col = -1; + int row = -1; + if (d->dropOn(event, &row, &col, &index)) { + if (d->model->dropMimeData(event->mimeData(), + dragDropMode() == InternalMove ? Qt::MoveAction : event->dropAction(), row, col, index)) { + if (dragDropMode() == InternalMove) + event->setDropAction(Qt::MoveAction); + event->accept(); + } + } + stopAutoScroll(); + setState(NoState); + d->viewport->update(); +} + +/*! + If the event hasn't already been accepted, determines the index to drop on. + + if (row == -1 && col == -1) + // append to this drop index + else + // place at row, col in drop index + + If it returns true a drop can be done, and dropRow, dropCol and dropIndex reflects the position of the drop. + \internal + */ +bool QAbstractItemViewPrivate::dropOn(QDropEvent *event, int *dropRow, int *dropCol, QModelIndex *dropIndex) +{ + Q_Q(QAbstractItemView); + if (event->isAccepted()) + return false; + + QModelIndex index; + // rootIndex() (i.e. the viewport) might be a valid index + if (viewport->rect().contains(event->pos())) { + index = q->indexAt(event->pos()); + if (!index.isValid() || !q->visualRect(index).contains(event->pos())) + index = root; + } + + // If we are allowed to do the drop + if (model->supportedDropActions() & event->dropAction()) { + int row = -1; + int col = -1; + if (index != root) { + dropIndicatorPosition = position(event->pos(), q->visualRect(index), index); + switch (dropIndicatorPosition) { + case QAbstractItemView::AboveItem: + row = index.row(); + col = index.column(); + index = index.parent(); + break; + case QAbstractItemView::BelowItem: + row = index.row() + 1; + col = index.column(); + index = index.parent(); + break; + case QAbstractItemView::OnItem: + case QAbstractItemView::OnViewport: + break; + } + } else { + dropIndicatorPosition = QAbstractItemView::OnViewport; + } + *dropIndex = index; + *dropRow = row; + *dropCol = col; + if (!droppingOnItself(event, index)) + return true; + } + return false; +} + +QAbstractItemView::DropIndicatorPosition +QAbstractItemViewPrivate::position(const QPoint &pos, const QRect &rect, const QModelIndex &index) const +{ + QAbstractItemView::DropIndicatorPosition r = QAbstractItemView::OnViewport; + if (!overwrite) { + const int margin = 2; + if (pos.y() - rect.top() < margin) { + r = QAbstractItemView::AboveItem; + } else if (rect.bottom() - pos.y() < margin) { + r = QAbstractItemView::BelowItem; + } else if (rect.contains(pos, true)) { + r = QAbstractItemView::OnItem; + } + } else { + QRect touchingRect = rect; + touchingRect.adjust(-1, -1, 1, 1); + if (touchingRect.contains(pos, false)) { + r = QAbstractItemView::OnItem; + } + } + + if (r == QAbstractItemView::OnItem && (!(model->flags(index) & Qt::ItemIsDropEnabled))) + r = pos.y() < rect.center().y() ? QAbstractItemView::AboveItem : QAbstractItemView::BelowItem; + + return r; +} + +#endif // QT_NO_DRAGANDDROP + +/*! + This function is called with the given \a event when the widget obtains the focus. + By default, the event is ignored. + + \sa setFocus(), focusOutEvent() +*/ +void QAbstractItemView::focusInEvent(QFocusEvent *event) +{ + Q_D(QAbstractItemView); + QAbstractScrollArea::focusInEvent(event); + if (selectionModel() + && !d->currentIndexSet + && !currentIndex().isValid()) { + bool autoScroll = d->autoScroll; + d->autoScroll = false; + QModelIndex index = moveCursor(MoveNext, Qt::NoModifier); // first visible index + if (index.isValid() && d->isIndexEnabled(index)) + selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + d->autoScroll = autoScroll; + } + d->viewport->update(); +} + +/*! + This function is called with the given \a event when the widget + looses the focus. By default, the event is ignored. + + \sa clearFocus(), focusInEvent() +*/ +void QAbstractItemView::focusOutEvent(QFocusEvent *event) +{ + Q_D(QAbstractItemView); + QAbstractScrollArea::focusOutEvent(event); + d->viewport->update(); +} + +/*! + This function is called with the given \a event when a key event is sent to + the widget. The default implementation handles basic cursor movement, e.g. Up, + Down, Left, Right, Home, PageUp, and PageDown; the activated() signal is + emitted if the current index is valid and the activation key is pressed + (e.g. Enter or Return, depending on the platform). + This function is where editing is initiated by key press, e.g. if F2 is + pressed. + + \sa edit(), moveCursor(), keyboardSearch(), tabKeyNavigation +*/ +void QAbstractItemView::keyPressEvent(QKeyEvent *event) +{ + Q_D(QAbstractItemView); + d->delayedAutoScroll.stop(); //any interaction with the view cancel the auto scrolling + +#ifdef QT_KEYPAD_NAVIGATION + switch (event->key()) { + case Qt::Key_Select: + if (QApplication::keypadNavigationEnabled()) { + if (!hasEditFocus()) { + setEditFocus(true); + return; + } + } + break; + case Qt::Key_Back: + if (QApplication::keypadNavigationEnabled() && hasEditFocus()) + setEditFocus(false); + else + event->ignore(); + return; + default: + if (QApplication::keypadNavigationEnabled() && !hasEditFocus()) { + event->ignore(); + return; + } + } +#endif + +#if !defined(QT_NO_CLIPBOARD) && !defined(QT_NO_SHORTCUT) + if (event == QKeySequence::Copy) { + QVariant variant; + if (d->model) + variant = d->model->data(currentIndex(), Qt::DisplayRole); + if (variant.type() == QVariant::String) + QApplication::clipboard()->setText(variant.toString()); + event->accept(); + } +#endif + + QPersistentModelIndex newCurrent; + switch (event->key()) { + case Qt::Key_Down: + newCurrent = moveCursor(MoveDown, event->modifiers()); + break; + case Qt::Key_Up: + newCurrent = moveCursor(MoveUp, event->modifiers()); + break; + case Qt::Key_Left: + newCurrent = moveCursor(MoveLeft, event->modifiers()); + break; + case Qt::Key_Right: + newCurrent = moveCursor(MoveRight, event->modifiers()); + break; + case Qt::Key_Home: + newCurrent = moveCursor(MoveHome, event->modifiers()); + break; + case Qt::Key_End: + newCurrent = moveCursor(MoveEnd, event->modifiers()); + break; + case Qt::Key_PageUp: + newCurrent = moveCursor(MovePageUp, event->modifiers()); + break; + case Qt::Key_PageDown: + newCurrent = moveCursor(MovePageDown, event->modifiers()); + break; + case Qt::Key_Tab: + if (d->tabKeyNavigation) + newCurrent = moveCursor(MoveNext, event->modifiers()); + break; + case Qt::Key_Backtab: + if (d->tabKeyNavigation) + newCurrent = moveCursor(MovePrevious, event->modifiers()); + break; + } + + QPersistentModelIndex oldCurrent = currentIndex(); + if (newCurrent != oldCurrent && newCurrent.isValid() && d->isIndexEnabled(newCurrent)) { + if (!hasFocus() && QApplication::focusWidget() == indexWidget(oldCurrent)) + setFocus(); + QItemSelectionModel::SelectionFlags command = selectionCommand(newCurrent, event); + if (command != QItemSelectionModel::NoUpdate + || style()->styleHint(QStyle::SH_ItemView_MovementWithoutUpdatingSelection, 0, this)) { + // note that we don't check if the new current index is enabled because moveCursor() makes sure it is + if (command & QItemSelectionModel::Current) { + d->selectionModel->setCurrentIndex(newCurrent, QItemSelectionModel::NoUpdate); + if (d->pressedPosition == QPoint(-1, -1)) + d->pressedPosition = visualRect(oldCurrent).center(); + QRect rect(d->pressedPosition - d->offset(), visualRect(newCurrent).center()); + setSelection(rect, command); + } else { + d->selectionModel->setCurrentIndex(newCurrent, command); + d->pressedPosition = visualRect(newCurrent).center() + d->offset(); + } + return; + } + } + + switch (event->key()) { + // ignored keys + case Qt::Key_Down: + case Qt::Key_Up: +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled()) { + event->accept(); // don't change focus + break; + } +#endif + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Home: + case Qt::Key_End: + case Qt::Key_PageUp: + case Qt::Key_PageDown: + case Qt::Key_Escape: + case Qt::Key_Shift: + case Qt::Key_Control: + event->ignore(); + break; + case Qt::Key_Space: + case Qt::Key_Select: + if (!edit(currentIndex(), AnyKeyPressed, event) && d->selectionModel) + d->selectionModel->select(currentIndex(), selectionCommand(currentIndex(), event)); +#ifdef QT_KEYPAD_NAVIGATION + if ( event->key()==Qt::Key_Select ) { + // Also do Key_Enter action. + if (currentIndex().isValid()) { + if (state() != EditingState) + emit activated(currentIndex()); + } else { + event->ignore(); + } + } +#endif + break; +#ifdef Q_WS_MAC + case Qt::Key_Enter: + case Qt::Key_Return: + // Propagate the enter if you couldn't edit the item and there are no + // current editors (if there are editors, the event was most likely propagated from it). + if (!edit(currentIndex(), EditKeyPressed, event) && d->editors.isEmpty()) + event->ignore(); + break; +#else + case Qt::Key_F2: + if (!edit(currentIndex(), EditKeyPressed, event)) + event->ignore(); + break; + case Qt::Key_Enter: + case Qt::Key_Return: + // ### we can't open the editor on enter, becuse + // some widgets will forward the enter event back + // to the viewport, starting an endless loop + if (state() != EditingState || hasFocus()) { + if (currentIndex().isValid()) + emit activated(currentIndex()); + event->ignore(); + } + break; +#endif + case Qt::Key_A: + if (event->modifiers() & Qt::ControlModifier) { + selectAll(); + break; + } + default: { +#ifdef Q_WS_MAC + if (event->key() == Qt::Key_O && event->modifiers() & Qt::ControlModifier && currentIndex().isValid()) { + emit activated(currentIndex()); + break; + } +#endif + bool modified = (event->modifiers() & (Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier)); + if (!event->text().isEmpty() && !modified) { + if (!edit(currentIndex(), AnyKeyPressed, event)) + keyboardSearch(event->text()); + } + event->ignore(); + break; } + } +} + +/*! + This function is called with the given \a event when a resize event is sent to + the widget. + + \sa QWidget::resizeEvent() +*/ +void QAbstractItemView::resizeEvent(QResizeEvent *event) +{ + QAbstractScrollArea::resizeEvent(event); + updateGeometries(); +} + +/*! + This function is called with the given \a event when a timer event is sent + to the widget. + + \sa QObject::timerEvent() +*/ +void QAbstractItemView::timerEvent(QTimerEvent *event) +{ + Q_D(QAbstractItemView); + if (event->timerId() == d->autoScrollTimer.timerId()) + doAutoScroll(); + else if (event->timerId() == d->updateTimer.timerId()) + d->updateDirtyRegion(); + else if (event->timerId() == d->delayedEditing.timerId()) { + d->delayedEditing.stop(); + edit(currentIndex()); + } else if (event->timerId() == d->delayedLayout.timerId()) { + d->delayedLayout.stop(); + if (isVisible()) { + d->interruptDelayedItemsLayout(); + doItemsLayout(); + const QModelIndex current = currentIndex(); + if (current.isValid() && d->state == QAbstractItemView::EditingState) + scrollTo(current); + } + } else if (event->timerId() == d->delayedAutoScroll.timerId()) { + d->delayedAutoScroll.stop(); + //end of the timer: if the current item is still the same as the one when the mouse press occurred + //we only get here if there was no double click + if (d->pressedIndex.isValid() && d->pressedIndex == currentIndex()) + scrollTo(d->pressedIndex); + } +} + +/*! + \reimp +*/ +void QAbstractItemView::inputMethodEvent(QInputMethodEvent *event) +{ + if (event->commitString().isEmpty() && event->preeditString().isEmpty()) { + event->ignore(); + return; + } + if (!edit(currentIndex(), AnyKeyPressed, event)) { + if (!event->commitString().isEmpty()) + keyboardSearch(event->commitString()); + event->ignore(); + } +} + +#ifndef QT_NO_DRAGANDDROP +/*! + \enum QAbstractItemView::DropIndicatorPosition + + This enum indicates the position of the drop indicator in + relation to the index at the current mouse position: + + \value OnItem The item will be dropped on the index. + + \value AboveItem The item will be dropped above the index. + + \value BelowItem The item will be dropped below the index. + + \value OnViewport The item will be dropped onto a region of the viewport with +no items. The way each view handles items dropped onto the viewport depends on +the behavior of the underlying model in use. +*/ + + +/*! + \since 4.1 + + Returns the position of the drop indicator in relation to the closest item. +*/ +QAbstractItemView::DropIndicatorPosition QAbstractItemView::dropIndicatorPosition() const +{ + Q_D(const QAbstractItemView); + return d->dropIndicatorPosition; +} +#endif + +/*! + This convenience function returns a list of all selected and + non-hidden item indexes in the view. The list contains no + duplicates, and is not sorted. + + \sa QItemSelectionModel::selectedIndexes() +*/ +QModelIndexList QAbstractItemView::selectedIndexes() const +{ + Q_D(const QAbstractItemView); + QModelIndexList indexes; + if (d->selectionModel) { + indexes = d->selectionModel->selectedIndexes(); + QList<QModelIndex>::iterator it = indexes.begin(); + while (it != indexes.end()) + if (isIndexHidden(*it)) + it = indexes.erase(it); + else + ++it; + } + return indexes; +} + +/*! + Starts editing the item at \a index, creating an editor if + necessary, and returns true if the view's \l{State} is now + EditingState; otherwise returns false. + + The action that caused the editing process is described by + \a trigger, and the associated event is specified by \a event. + + Editing can be forced by specifying the \a trigger to be + QAbstractItemView::AllEditTriggers. + + \sa closeEditor() +*/ +bool QAbstractItemView::edit(const QModelIndex &index, EditTrigger trigger, QEvent *event) +{ + Q_D(QAbstractItemView); + + if (!d->isIndexValid(index)) + return false; + + if (QWidget *w = (d->persistent.isEmpty() ? static_cast<QWidget*>(0) : d->editorForIndex(index).editor.data())) { + if (w->focusPolicy() == Qt::NoFocus) + return false; + w->setFocus(); + return true; + } + + if (trigger == DoubleClicked) { + d->delayedEditing.stop(); + d->delayedAutoScroll.stop(); + } else if (trigger == CurrentChanged) { + d->delayedEditing.stop(); + } + + if (d->sendDelegateEvent(index, event)) { + d->viewport->update(visualRect(index)); + return true; + } + + // save the previous trigger before updating + EditTriggers lastTrigger = d->lastTrigger; + d->lastTrigger = trigger; + + if (!d->shouldEdit(trigger, d->model->buddy(index))) + return false; + + if (d->delayedEditing.isActive()) + return false; + + // we will receive a mouseButtonReleaseEvent after a + // mouseDoubleClickEvent, so we need to check the previous trigger + if (lastTrigger == DoubleClicked && trigger == SelectedClicked) + return false; + + // we may get a double click event later + if (trigger == SelectedClicked) + d->delayedEditing.start(QApplication::doubleClickInterval(), this); + else + d->openEditor(index, d->shouldForwardEvent(trigger, event) ? event : 0); + + return true; +} + +/*! + \internal + Updates the data shown in the open editor widgets in the view. +*/ +void QAbstractItemView::updateEditorData() +{ + Q_D(QAbstractItemView); + d->updateEditorData(QModelIndex(), QModelIndex()); +} + +/*! + \internal + Updates the geometry of the open editor widgets in the view. +*/ +void QAbstractItemView::updateEditorGeometries() +{ + Q_D(QAbstractItemView); + if(d->editors.isEmpty()) + return; + QStyleOptionViewItemV4 option = d->viewOptionsV4(); + QList<QEditorInfo>::iterator it = d->editors.begin(); + QWidgetList editorsToRelease; + while (it != d->editors.end()) { + QModelIndex index = it->index; + QWidget *editor = it->editor; + if (index.isValid() && editor) { + option.rect = visualRect(index); + if (option.rect.isValid()) { + editor->show(); + QAbstractItemDelegate *delegate = d->delegateForIndex(index); + if (delegate) + delegate->updateEditorGeometry(editor, option, index); + } else { + editor->hide(); + } + ++it; + } else { + it = d->editors.erase(it); + editorsToRelease << editor; + } + } + + //we release the editor outside of the loop because it might change the focus and try + //to change the d->editors list. + foreach(QWidget *editor, editorsToRelease) { + d->releaseEditor(editor); + } +} + +/*! + \since 4.4 + + Updates the geometry of the child widgets of the view. +*/ +void QAbstractItemView::updateGeometries() +{ + updateEditorGeometries(); + QMetaObject::invokeMethod(this, "_q_fetchMore", Qt::QueuedConnection); +} + +/*! + \internal +*/ +void QAbstractItemView::verticalScrollbarValueChanged(int value) +{ + Q_D(QAbstractItemView); + if (verticalScrollBar()->maximum() == value && d->model->canFetchMore(d->root)) + d->model->fetchMore(d->root); +} + +/*! + \internal +*/ +void QAbstractItemView::horizontalScrollbarValueChanged(int value) +{ + Q_D(QAbstractItemView); + if (horizontalScrollBar()->maximum() == value && d->model->canFetchMore(d->root)) + d->model->fetchMore(d->root); +} + +/*! + \internal +*/ +void QAbstractItemView::verticalScrollbarAction(int) +{ + //do nothing +} + +/*! + \internal +*/ +void QAbstractItemView::horizontalScrollbarAction(int) +{ + //do nothing +} + +/*! + Closes the given \a editor, and releases it. The \a hint is + used to specify how the view should respond to the end of the editing + operation. For example, the hint may indicate that the next item in + the view should be opened for editing. + + \sa edit(), commitData() +*/ + +void QAbstractItemView::closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint) +{ + Q_D(QAbstractItemView); + + // Close the editor + if (editor) { + bool isPersistent = d->persistent.contains(editor); + bool hadFocus = editor->hasFocus(); + QModelIndex index = d->indexForEditor(editor); + if (!index.isValid()) + return; // the editor was not registered + + if (!isPersistent) { + setState(NoState); + QModelIndex index = d->indexForEditor(editor); + editor->removeEventFilter(d->delegateForIndex(index)); + d->removeEditor(editor); + } + if (hadFocus) + setFocus(); // this will send a focusLost event to the editor + else + d->checkPersistentEditorFocus(); + + QPointer<QWidget> ed = editor; + QApplication::sendPostedEvents(editor, 0); + editor = ed; + + if (!isPersistent && editor) + d->releaseEditor(editor); + } + + // The EndEditHint part + QItemSelectionModel::SelectionFlags flags = QItemSelectionModel::ClearAndSelect + | d->selectionBehaviorFlags(); + switch (hint) { + case QAbstractItemDelegate::EditNextItem: { + QModelIndex index = moveCursor(MoveNext, Qt::NoModifier); + if (index.isValid()) { + QPersistentModelIndex persistent(index); + d->selectionModel->setCurrentIndex(persistent, flags); + // currentChanged signal would have already started editing + if (index.flags() & Qt::ItemIsEditable + && (!(editTriggers() & QAbstractItemView::CurrentChanged))) + edit(persistent); + } break; } + case QAbstractItemDelegate::EditPreviousItem: { + QModelIndex index = moveCursor(MovePrevious, Qt::NoModifier); + if (index.isValid()) { + QPersistentModelIndex persistent(index); + d->selectionModel->setCurrentIndex(persistent, flags); + // currentChanged signal would have already started editing + if (index.flags() & Qt::ItemIsEditable + && (!(editTriggers() & QAbstractItemView::CurrentChanged))) + edit(persistent); + } break; } + case QAbstractItemDelegate::SubmitModelCache: + d->model->submit(); + break; + case QAbstractItemDelegate::RevertModelCache: + d->model->revert(); + break; + default: + break; + } +} + +/*! + Commit the data in the \a editor to the model. + + \sa closeEditor() +*/ +void QAbstractItemView::commitData(QWidget *editor) +{ + Q_D(QAbstractItemView); + if (!editor || !d->itemDelegate || d->currentlyCommittingEditor) + return; + QModelIndex index = d->indexForEditor(editor); + if (!index.isValid()) + return; + d->currentlyCommittingEditor = editor; + QAbstractItemDelegate *delegate = d->delegateForIndex(index); + editor->removeEventFilter(delegate); + delegate->setModelData(editor, d->model, index); + editor->installEventFilter(delegate); + d->currentlyCommittingEditor = 0; +} + +/*! + This function is called when the given \a editor has been destroyed. + + \sa closeEditor() +*/ +void QAbstractItemView::editorDestroyed(QObject *editor) +{ + Q_D(QAbstractItemView); + QWidget *w = qobject_cast<QWidget*>(editor); + d->removeEditor(w); + d->persistent.remove(w); + if (state() == EditingState) + setState(NoState); +} + +/*! + \obsolete + Sets the horizontal scroll bar's steps per item to \a steps. + + This is the number of steps used by the horizontal scroll bar to + represent the width of an item. + + Note that if the view has a horizontal header, the item steps + will be ignored and the header section size will be used instead. + + \sa horizontalStepsPerItem() setVerticalStepsPerItem() +*/ +void QAbstractItemView::setHorizontalStepsPerItem(int steps) +{ + Q_UNUSED(steps); + // do nothing +} + +/*! + \obsolete + Returns the horizontal scroll bar's steps per item. + + \sa setHorizontalStepsPerItem() verticalStepsPerItem() +*/ +int QAbstractItemView::horizontalStepsPerItem() const +{ + return 1; +} + +/*! + \obsolete + Sets the vertical scroll bar's steps per item to \a steps. + + This is the number of steps used by the vertical scroll bar to + represent the height of an item. + + Note that if the view has a vertical header, the item steps + will be ignored and the header section size will be used instead. + + \sa verticalStepsPerItem() setHorizontalStepsPerItem() +*/ +void QAbstractItemView::setVerticalStepsPerItem(int steps) +{ + Q_UNUSED(steps); + // do nothing +} + +/*! + \obsolete + Returns the vertical scroll bar's steps per item. + + \sa setVerticalStepsPerItem() horizontalStepsPerItem() +*/ +int QAbstractItemView::verticalStepsPerItem() const +{ + return 1; +} + +/*! + Moves to and selects the item best matching the string \a search. + If no item is found nothing happens. + + In the default implementation, the search is reset if \a search is empty, or + the time interval since the last search has exceeded + QApplication::keyboardInputInterval(). +*/ +void QAbstractItemView::keyboardSearch(const QString &search) +{ + Q_D(QAbstractItemView); + if (!d->model->rowCount(d->root) || !d->model->columnCount(d->root)) + return; + + QModelIndex start = currentIndex().isValid() ? currentIndex() + : d->model->index(0, 0, d->root); + QTime now(QTime::currentTime()); + bool skipRow = false; + if (search.isEmpty() + || (d->keyboardInputTime.msecsTo(now) > QApplication::keyboardInputInterval())) { + d->keyboardInput = search; + skipRow = true; + } else { + d->keyboardInput += search; + } + d->keyboardInputTime = now; + + // special case for searches with same key like 'aaaaa' + bool sameKey = false; + if (d->keyboardInput.length() > 1) { + int c = d->keyboardInput.count(d->keyboardInput.at(d->keyboardInput.length() - 1)); + sameKey = (c == d->keyboardInput.length()); + if (sameKey) + skipRow = true; + } + + // skip if we are searching for the same key or a new search started + if (skipRow) { + QModelIndex parent = start.parent(); + int newRow = (start.row() < d->model->rowCount(parent) - 1) ? start.row() + 1 : 0; + start = d->model->index(newRow, start.column(), parent); + } + + // search from start with wraparound + const QString searchString = sameKey ? QString(d->keyboardInput.at(0)) : d->keyboardInput; + QModelIndex current = start; + QModelIndexList match; + QModelIndex firstMatch; + QModelIndexList previous; + do { + match = d->model->match(current, Qt::DisplayRole, searchString); + if (match == previous) + break; + firstMatch = match.value(0); + previous = match; + if (firstMatch.isValid()) { + if (d->isIndexEnabled(firstMatch)) { + setCurrentIndex(firstMatch); + break; + } + int row = firstMatch.row() + 1; + if (row >= d->model->rowCount(firstMatch.parent())) + row = 0; + current = firstMatch.sibling(row, firstMatch.column()); + } + } while (current != start && firstMatch.isValid()); +} + +/*! + Returns the size hint for the item with the specified \a index or + an invalid size for invalid indexes. + + \sa sizeHintForRow(), sizeHintForColumn() +*/ +QSize QAbstractItemView::sizeHintForIndex(const QModelIndex &index) const +{ + Q_D(const QAbstractItemView); + if (!d->isIndexValid(index) || !d->itemDelegate) + return QSize(); + return d->delegateForIndex(index)->sizeHint(d->viewOptionsV4(), index); +} + +/*! + Returns the height size hint for the specified \a row or -1 if + there is no model. + + The returned height is calculated using the size hints of the + given \a row's items, i.e. the returned value is the maximum + height among the items. Note that to control the height of a row, + you must reimplement the QAbstractItemDelegate::sizeHint() + function. + + This function is used in views with a vertical header to find the + size hint for a header section based on the contents of the given + \a row. + + \sa sizeHintForColumn() +*/ +int QAbstractItemView::sizeHintForRow(int row) const +{ + Q_D(const QAbstractItemView); + + if (row < 0 || row >= d->model->rowCount() || !model()) + return -1; + + QStyleOptionViewItemV4 option = d->viewOptionsV4(); + int height = 0; + int colCount = d->model->columnCount(d->root); + QModelIndex index; + for (int c = 0; c < colCount; ++c) { + index = d->model->index(row, c, d->root); + if (QWidget *editor = d->editorForIndex(index).editor) + height = qMax(height, editor->size().height()); + int hint = d->delegateForIndex(index)->sizeHint(option, index).height(); + height = qMax(height, hint); + } + return height; +} + +/*! + Returns the width size hint for the specified \a column or -1 if there is no model. + + This function is used in views with a horizontal header to find the size hint for + a header section based on the contents of the given \a column. + + \sa sizeHintForRow() +*/ +int QAbstractItemView::sizeHintForColumn(int column) const +{ + Q_D(const QAbstractItemView); + + if (column < 0 || column >= d->model->columnCount() || !model()) + return -1; + + QStyleOptionViewItemV4 option = d->viewOptionsV4(); + int width = 0; + int rows = d->model->rowCount(d->root); + QModelIndex index; + for (int r = 0; r < rows; ++r) { + index = d->model->index(r, column, d->root); + if (QWidget *editor = d->editorForIndex(index).editor) + width = qMax(width, editor->sizeHint().width()); + int hint = d->delegateForIndex(index)->sizeHint(option, index).width(); + width = qMax(width, hint); + } + return width; +} + +/*! + Opens a persistent editor on the item at the given \a index. + If no editor exists, the delegate will create a new editor. + + \sa closePersistentEditor() +*/ +void QAbstractItemView::openPersistentEditor(const QModelIndex &index) +{ + Q_D(QAbstractItemView); + QStyleOptionViewItemV4 options = d->viewOptionsV4(); + options.rect = visualRect(index); + options.state |= (index == currentIndex() ? QStyle::State_HasFocus : QStyle::State_None); + + QWidget *editor = d->editor(index, options); + if (editor) { + editor->show(); + d->persistent.insert(editor); + } +} + +/*! + Closes the persistent editor for the item at the given \a index. + + \sa openPersistentEditor() +*/ +void QAbstractItemView::closePersistentEditor(const QModelIndex &index) +{ + Q_D(QAbstractItemView); + QWidget *editor = d->editorForIndex(index).editor; + if (editor) { + if (index == selectionModel()->currentIndex()) + closeEditor(editor, QAbstractItemDelegate::RevertModelCache); + d->persistent.remove(editor); + d->removeEditor(editor); + d->releaseEditor(editor); + } +} + +/*! + \since 4.1 + + Sets the given \a widget on the item at the given \a index, passing the + ownership of the widget to the viewport. + + If \a index is invalid (e.g., if you pass the root index), this function + will do nothing. + + The given \a widget's \l{QWidget}{autoFillBackground} property must be set + to true, otherwise the widget's background will be transparent, showing + both the model data and the item at the given \a index. + + If index widget A is replaced with index widget B, index widget A will be + deleted. For example, in the code snippet below, the QLineEdit object will + be deleted. + + \snippet doc/src/snippets/code/src_gui_itemviews_qabstractitemview.cpp 1 + + This function should only be used to display static content within the + visible area corresponding to an item of data. If you want to display + custom dynamic content or implement a custom editor widget, subclass + QItemDelegate instead. + + \sa {Delegate Classes} +*/ +void QAbstractItemView::setIndexWidget(const QModelIndex &index, QWidget *widget) +{ + Q_D(QAbstractItemView); + if (!d->isIndexValid(index)) + return; + if (QWidget *oldWidget = indexWidget(index)) { + d->removeEditor(oldWidget); + oldWidget->deleteLater(); + } + if (widget) { + widget->setParent(viewport()); + d->persistent.insert(widget); + d->addEditor(index, widget, true); + widget->show(); + if (!d->delayedPendingLayout) + widget->setGeometry(visualRect(index)); + dataChanged(index, index); // update the geometry + } +} + +/*! + \since 4.1 + + Returns the widget for the item at the given \a index. +*/ +QWidget* QAbstractItemView::indexWidget(const QModelIndex &index) const +{ + Q_D(const QAbstractItemView); + if (!d->isIndexValid(index)) + return 0; + return d->editorForIndex(index).editor; +} + +/*! + \since 4.1 + + Scrolls the view to the top. + + \sa scrollTo(), scrollToBottom() +*/ +void QAbstractItemView::scrollToTop() +{ + verticalScrollBar()->setValue(verticalScrollBar()->minimum()); +} + +/*! + \since 4.1 + + Scrolls the view to the bottom. + + \sa scrollTo(), scrollToTop() +*/ +void QAbstractItemView::scrollToBottom() +{ + Q_D(QAbstractItemView); + if (d->delayedPendingLayout) { + d->executePostedLayout(); + updateGeometries(); + } + verticalScrollBar()->setValue(verticalScrollBar()->maximum()); +} + +/*! + \since 4.3 + + Updates the area occupied by the given \a index. + +*/ +void QAbstractItemView::update(const QModelIndex &index) +{ + Q_D(QAbstractItemView); + if (index.isValid()) + d->viewport->update(visualRect(index)); +} + +/*! + This slot is called when items are changed in the model. The + changed items are those from \a topLeft to \a bottomRight + inclusive. If just one item is changed \a topLeft == \a + bottomRight. +*/ +void QAbstractItemView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + // Single item changed + Q_D(QAbstractItemView); + if (topLeft == bottomRight && topLeft.isValid()) { + const QEditorInfo editorInfo = d->editorForIndex(topLeft); + //we don't update the edit data if it is static + if (!editorInfo.isStatic && editorInfo.editor) { + QAbstractItemDelegate *delegate = d->delegateForIndex(topLeft); + if (delegate) { + delegate->setEditorData(editorInfo.editor, topLeft); + } + } + if (isVisible() && !d->delayedPendingLayout) { + // otherwise the items will be update later anyway + d->viewport->update(visualRect(topLeft)); + } + return; + } + d->updateEditorData(topLeft, bottomRight); + if (!isVisible() || d->delayedPendingLayout) + return; // no need to update + d->viewport->update(); +} + +/*! + This slot is called when rows are inserted. The new rows are those + under the given \a parent from \a start to \a end inclusive. The + base class implementation calls fetchMore() on the model to check + for more data. + + \sa rowsAboutToBeRemoved() +*/ +void QAbstractItemView::rowsInserted(const QModelIndex &, int, int) +{ + if (!isVisible()) + QMetaObject::invokeMethod(this, "_q_fetchMore", Qt::QueuedConnection); + else + updateEditorGeometries(); +} + +/*! + This slot is called when rows are about to be removed. The deleted rows are + those under the given \a parent from \a start to \a end inclusive. + + \sa rowsInserted() +*/ +void QAbstractItemView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + Q_D(QAbstractItemView); + + setState(CollapsingState); + + // Ensure one selected item in single selection mode. + QModelIndex current = currentIndex(); + if (d->selectionMode == SingleSelection + && current.isValid() + && current.row() >= start + && current.row() <= end + && current.parent() == parent) { + int totalToRemove = end - start + 1; + if (d->model->rowCount(parent) <= totalToRemove) { // no more children + QModelIndex index = parent; + while (index != d->root && !d->isIndexEnabled(index)) + index = index.parent(); + if (index != d->root) + setCurrentIndex(index); + } else { + int row = end + 1; + QModelIndex next; + do { // find the next visible and enabled item + next = d->model->index(row++, current.column(), current.parent()); + } while (next.isValid() && (isIndexHidden(next) || !d->isIndexEnabled(next))); + if (row > d->model->rowCount(parent)) { + row = start - 1; + do { // find the previous visible and enabled item + next = d->model->index(row--, current.column(), current.parent()); + } while (next.isValid() && (isIndexHidden(next) || !d->isIndexEnabled(next))); + } + setCurrentIndex(next); + } + } + + // Remove all affected editors; this is more efficient than waiting for updateGeometries() to clean out editors for invalid indexes + for (int i = d->editors.size() - 1; i >= 0; --i) { + const QModelIndex index = d->editors.at(i).index; + QWidget *editor = d->editors.at(i).editor; + if (index.row() >= start && index.row() <= end && d->model->parent(index) == parent) { + d->editors.removeAt(i); + d->releaseEditor(editor); + } + } +} + +/*! + \internal + + This slot is called when rows have been removed. The deleted + rows are those under the given \a parent from \a start to \a end + inclusive. +*/ +void QAbstractItemViewPrivate::_q_rowsRemoved(const QModelIndex &, int, int) +{ + Q_Q(QAbstractItemView); + if (q->isVisible()) + q->updateEditorGeometries(); + q->setState(QAbstractItemView::NoState); +} + +/*! + \internal + + This slot is called when columns are about to be removed. The deleted + columns are those under the given \a parent from \a start to \a end + inclusive. +*/ +void QAbstractItemViewPrivate::_q_columnsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + Q_Q(QAbstractItemView); + + q->setState(QAbstractItemView::CollapsingState); + + // Ensure one selected item in single selection mode. + QModelIndex current = q->currentIndex(); + if (current.isValid() + && selectionMode == QAbstractItemView::SingleSelection + && current.column() >= start + && current.column() <= end) { + int totalToRemove = end - start + 1; + if (model->columnCount(parent) < totalToRemove) { // no more columns + QModelIndex index = parent; + while (index.isValid() && !isIndexEnabled(index)) + index = index.parent(); + if (index.isValid()) + q->setCurrentIndex(index); + } else { + int column = end; + QModelIndex next; + do { // find the next visible and enabled item + next = model->index(current.row(), column++, current.parent()); + } while (next.isValid() && (q->isIndexHidden(next) || !isIndexEnabled(next))); + q->setCurrentIndex(next); + } + } + + // Remove all affected editors; this is more efficient than waiting for updateGeometries() to clean out editors for invalid indexes + QList<QEditorInfo>::iterator it = editors.begin(); + while (it != editors.end()) { + QModelIndex index = it->index; + if (index.column() <= start && index.column() >= end && model->parent(index) == parent) { + QWidget *editor = it->editor; + it = editors.erase(it); + releaseEditor(editor); + } else { + ++it; + } + } +} + +/*! + \internal + + This slot is called when columns have been removed. The deleted + rows are those under the given \a parent from \a start to \a end + inclusive. +*/ +void QAbstractItemViewPrivate::_q_columnsRemoved(const QModelIndex &, int, int) +{ + Q_Q(QAbstractItemView); + if (q->isVisible()) + q->updateEditorGeometries(); + q->setState(QAbstractItemView::NoState); +} + +/*! + \internal + + This slot is called when rows have been inserted. +*/ +void QAbstractItemViewPrivate::_q_columnsInserted(const QModelIndex &, int, int) +{ + Q_Q(QAbstractItemView); + if (q->isVisible()) + q->updateEditorGeometries(); +} + + + +/*! + \internal +*/ +void QAbstractItemViewPrivate::_q_modelDestroyed() +{ + Q_Q(QAbstractItemView); + model = QAbstractItemModelPrivate::staticEmptyModel(); + QMetaObject::invokeMethod(q, "reset", Qt::QueuedConnection); +} + +/*! + \internal + + This slot is called when the layout is changed. +*/ +void QAbstractItemViewPrivate::_q_layoutChanged() +{ + doDelayedItemsLayout(); +} + +/*! + This slot is called when the selection is changed. The previous + selection (which may be empty), is specified by \a deselected, and the + new selection by \a selected. + + \sa setSelection() +*/ +void QAbstractItemView::selectionChanged(const QItemSelection &selected, + const QItemSelection &deselected) +{ + Q_D(QAbstractItemView); + if (isVisible() && updatesEnabled()) { + d->setDirtyRegion(visualRegionForSelection(deselected)); + d->setDirtyRegion(visualRegionForSelection(selected)); + d->updateDirtyRegion(); + } +} + +/*! + This slot is called when a new item becomes the current item. + The previous current item is specified by the \a previous index, and the new + item by the \a current index. + + If you want to know about changes to items see the + dataChanged() signal. +*/ +void QAbstractItemView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ + Q_D(QAbstractItemView); + Q_ASSERT(d->model); + + if (previous.isValid()) { + QModelIndex buddy = d->model->buddy(previous); + QWidget *editor = d->editorForIndex(buddy).editor; + if (editor && !d->persistent.contains(editor)) { + commitData(editor); + if (current.row() != previous.row()) + closeEditor(editor, QAbstractItemDelegate::SubmitModelCache); + else + closeEditor(editor, QAbstractItemDelegate::NoHint); + } + if (isVisible()) { + d->setDirtyRegion(visualRect(previous)); + d->updateDirtyRegion(); + } + } + if (isVisible() && current.isValid() && !d->autoScrollTimer.isActive()) { + if (d->autoScroll) + scrollTo(current); + d->setDirtyRegion(visualRect(current)); + d->updateDirtyRegion(); + edit(current, CurrentChanged, 0); + if (current.row() == (d->model->rowCount(d->root) - 1)) + d->_q_fetchMore(); + } +} + +#ifndef QT_NO_DRAGANDDROP +/*! + Starts a drag by calling drag->exec() using the given \a supportedActions. +*/ +void QAbstractItemView::startDrag(Qt::DropActions supportedActions) +{ + Q_D(QAbstractItemView); + QModelIndexList indexes = d->selectedDraggableIndexes(); + if (indexes.count() > 0) { + QMimeData *data = d->model->mimeData(indexes); + if (!data) + return; + QRect rect; + QPixmap pixmap = d->renderToPixmap(indexes, &rect); + rect.adjust(horizontalOffset(), verticalOffset(), 0, 0); + QDrag *drag = new QDrag(this); + drag->setPixmap(pixmap); + drag->setMimeData(data); + drag->setHotSpot(d->pressedPosition - rect.topLeft()); + Qt::DropAction defaultDropAction = Qt::IgnoreAction; + if (supportedActions & Qt::CopyAction && dragDropMode() != QAbstractItemView::InternalMove) + defaultDropAction = Qt::CopyAction; + if (drag->exec(supportedActions, defaultDropAction) == Qt::MoveAction) + d->clearOrRemove(); + } +} +#endif // QT_NO_DRAGANDDROP + +/*! + Returns a QStyleOptionViewItem structure populated with the view's + palette, font, state, alignments etc. +*/ +QStyleOptionViewItem QAbstractItemView::viewOptions() const +{ + Q_D(const QAbstractItemView); + QStyleOptionViewItem option; + option.init(this); + option.state &= ~QStyle::State_MouseOver; + option.font = font(); + +#ifdef Q_WS_WIN + // Note this is currently required on Windows + // do give non-focused item views inactive appearance + if (!hasFocus()) + option.state &= ~QStyle::State_Active; +#endif + + option.state &= ~QStyle::State_HasFocus; + if (d->iconSize.isValid()) { + option.decorationSize = d->iconSize; + } else { + int pm = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this); + option.decorationSize = QSize(pm, pm); + } + option.decorationPosition = QStyleOptionViewItem::Left; + option.decorationAlignment = Qt::AlignCenter; + option.displayAlignment = Qt::AlignLeft|Qt::AlignVCenter; + option.textElideMode = d->textElideMode; + option.rect = QRect(); + option.showDecorationSelected = style()->styleHint(QStyle::SH_ItemView_ShowDecorationSelected, 0, this); + return option; +} + +QStyleOptionViewItemV4 QAbstractItemViewPrivate::viewOptionsV4() const +{ + Q_Q(const QAbstractItemView); + QStyleOptionViewItemV4 option = q->viewOptions(); + if (wrapItemText) + option.features = QStyleOptionViewItemV2::WrapText; + option.locale = q->locale(); + option.locale.setNumberOptions(QLocale::OmitGroupSeparator); + option.widget = q; + return option; +} + +/*! + Returns the item view's state. + + \sa setState() +*/ +QAbstractItemView::State QAbstractItemView::state() const +{ + Q_D(const QAbstractItemView); + return d->state; +} + +/*! + Sets the item view's state to the given \a state. + + \sa state() +*/ +void QAbstractItemView::setState(State state) +{ + Q_D(QAbstractItemView); + d->state = state; +} + +/*! + Schedules a layout of the items in the view to be executed when the + event processing starts. + + Even if scheduleDelayedItemsLayout() is called multiple times before + events are processed, the view will only do the layout once. + + \sa executeDelayedItemsLayout() +*/ +void QAbstractItemView::scheduleDelayedItemsLayout() +{ + Q_D(QAbstractItemView); + d->doDelayedItemsLayout(); +} + +/*! + Executes the scheduled layouts without waiting for the event processing + to begin. + + \sa scheduleDelayedItemsLayout() +*/ +void QAbstractItemView::executeDelayedItemsLayout() +{ + Q_D(QAbstractItemView); + d->executePostedLayout(); +} + +/*! + \since 4.1 + + Marks the given \a region as dirty and schedules it to be updated. + You only need to call this function if you are implementing + your own view subclass. + + \sa scrollDirtyRegion(), dirtyRegionOffset() +*/ + +void QAbstractItemView::setDirtyRegion(const QRegion ®ion) +{ + Q_D(QAbstractItemView); + d->setDirtyRegion(region); +} + +/*! + Prepares the view for scrolling by (\a{dx},\a{dy}) pixels by moving the dirty regions in the + opposite direction. You only need to call this function if you are implementing a scrolling + viewport in your view subclass. + + If you implement scrollContentsBy() in a subclass of QAbstractItemView, call this function + before you call QWidget::scroll() on the viewport. Alternatively, just call update(). + + \sa scrollContentsBy(), dirtyRegionOffset(), setDirtyRegion() +*/ +void QAbstractItemView::scrollDirtyRegion(int dx, int dy) +{ + Q_D(QAbstractItemView); + d->scrollDirtyRegion(dx, dy); +} + +/*! + Returns the offset of the dirty regions in the view. + + If you use scrollDirtyRegion() and implement a paintEvent() in a subclass of + QAbstractItemView, you should translate the area given by the paint event with + the offset returned from this function. + + \sa scrollDirtyRegion(), setDirtyRegion() +*/ +QPoint QAbstractItemView::dirtyRegionOffset() const +{ + Q_D(const QAbstractItemView); + return d->scrollDelayOffset; +} + +/*! + \internal +*/ +void QAbstractItemView::startAutoScroll() +{ + Q_D(QAbstractItemView); + // ### it would be nice to make this into a style hint one day + int scrollInterval = (verticalScrollMode() == QAbstractItemView::ScrollPerItem) ? 150 : 50; + d->autoScrollTimer.start(scrollInterval, this); + d->autoScrollCount = 0; +} + +/*! + \internal +*/ +void QAbstractItemView::stopAutoScroll() +{ + Q_D(QAbstractItemView); + d->autoScrollTimer.stop(); + d->autoScrollCount = 0; +} + +/*! + \internal +*/ +void QAbstractItemView::doAutoScroll() +{ + // find how much we should scroll with + Q_D(QAbstractItemView); + int verticalStep = verticalScrollBar()->pageStep(); + int horizontalStep = horizontalScrollBar()->pageStep(); + if (d->autoScrollCount < qMax(verticalStep, horizontalStep)) + ++d->autoScrollCount; + + int margin = d->autoScrollMargin; + int verticalValue = verticalScrollBar()->value(); + int horizontalValue = horizontalScrollBar()->value(); + + QPoint pos = d->viewport->mapFromGlobal(QCursor::pos()); + QRect area = static_cast<QAbstractItemView*>(d->viewport)->d_func()->clipRect(); // access QWidget private by bending C++ rules + + // do the scrolling if we are in the scroll margins + if (pos.y() - area.top() < margin) + verticalScrollBar()->setValue(verticalValue - d->autoScrollCount); + else if (area.bottom() - pos.y() < margin) + verticalScrollBar()->setValue(verticalValue + d->autoScrollCount); + if (pos.x() - area.left() < margin) + horizontalScrollBar()->setValue(horizontalValue - d->autoScrollCount); + else if (area.right() - pos.x() < margin) + horizontalScrollBar()->setValue(horizontalValue + d->autoScrollCount); + // if nothing changed, stop scrolling + bool verticalUnchanged = (verticalValue == verticalScrollBar()->value()); + bool horizontalUnchanged = (horizontalValue == horizontalScrollBar()->value()); + if (verticalUnchanged && horizontalUnchanged) { + stopAutoScroll(); + } else { +#ifndef QT_NO_DRAGANDDROP + d->dropIndicatorRect = QRect(); + d->dropIndicatorPosition = QAbstractItemView::OnViewport; +#endif + d->viewport->update(); + } +} + +/*! + Returns the SelectionFlags to be used when updating a selection with + to include the \a index specified. The \a event is a user input event, + such as a mouse or keyboard event. + + Reimplement this function to define your own selection behavior. + + \sa setSelection() +*/ +QItemSelectionModel::SelectionFlags QAbstractItemView::selectionCommand(const QModelIndex &index, + const QEvent *event) const +{ + Q_D(const QAbstractItemView); + switch (d->selectionMode) { + case NoSelection: // Never update selection model + return QItemSelectionModel::NoUpdate; + case SingleSelection: // ClearAndSelect on valid index otherwise NoUpdate + if (event && event->type() == QEvent::MouseButtonRelease) + return QItemSelectionModel::NoUpdate; + return QItemSelectionModel::ClearAndSelect|d->selectionBehaviorFlags(); + case MultiSelection: + return d->multiSelectionCommand(index, event); + case ExtendedSelection: + return d->extendedSelectionCommand(index, event); + case ContiguousSelection: + return d->contiguousSelectionCommand(index, event); + } + return QItemSelectionModel::NoUpdate; +} + +QItemSelectionModel::SelectionFlags QAbstractItemViewPrivate::multiSelectionCommand( + const QModelIndex &index, const QEvent *event) const +{ + Q_UNUSED(index); + + if (event) { + switch (event->type()) { + case QEvent::KeyPress: + if (static_cast<const QKeyEvent*>(event)->key() == Qt::Key_Space + || static_cast<const QKeyEvent*>(event)->key() == Qt::Key_Select) + return QItemSelectionModel::Toggle|selectionBehaviorFlags(); + break; + case QEvent::MouseButtonPress: + if (static_cast<const QMouseEvent*>(event)->button() == Qt::LeftButton) + return QItemSelectionModel::Toggle|selectionBehaviorFlags(); // toggle + break; + case QEvent::MouseButtonRelease: + if (static_cast<const QMouseEvent*>(event)->button() == Qt::LeftButton) + return QItemSelectionModel::NoUpdate|selectionBehaviorFlags(); // finalize + break; + case QEvent::MouseMove: + if (static_cast<const QMouseEvent*>(event)->buttons() & Qt::LeftButton) + return QItemSelectionModel::ToggleCurrent|selectionBehaviorFlags(); // toggle drag select + default: + break; + } + return QItemSelectionModel::NoUpdate; + } + + return QItemSelectionModel::Toggle|selectionBehaviorFlags(); +} + +QItemSelectionModel::SelectionFlags QAbstractItemViewPrivate::extendedSelectionCommand( + const QModelIndex &index, const QEvent *event) const +{ + Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers(); + if (event) { + switch (event->type()) { + case QEvent::MouseMove: { + // Toggle on MouseMove + modifiers = static_cast<const QMouseEvent*>(event)->modifiers(); + if (modifiers & Qt::ControlModifier) + return QItemSelectionModel::ToggleCurrent|selectionBehaviorFlags(); + break; + } + case QEvent::MouseButtonPress: { + modifiers = static_cast<const QMouseEvent*>(event)->modifiers(); + const Qt::MouseButton button = static_cast<const QMouseEvent*>(event)->button(); + const bool rightButtonPressed = button & Qt::RightButton; + const bool shiftKeyPressed = modifiers & Qt::ShiftModifier; + const bool controlKeyPressed = modifiers & Qt::ControlModifier; + const bool indexIsSelected = selectionModel->isSelected(index); + if ((shiftKeyPressed || controlKeyPressed) && rightButtonPressed) + return QItemSelectionModel::NoUpdate; + if (!shiftKeyPressed && !controlKeyPressed && indexIsSelected) + return QItemSelectionModel::NoUpdate; + if (!index.isValid() && !rightButtonPressed && !shiftKeyPressed && !controlKeyPressed) + return QItemSelectionModel::Clear; + if (!index.isValid()) + return QItemSelectionModel::NoUpdate; + break; + } + case QEvent::MouseButtonRelease: { + // ClearAndSelect on MouseButtonRelease if MouseButtonPress on selected item or empty area + modifiers = static_cast<const QMouseEvent*>(event)->modifiers(); + const Qt::MouseButton button = static_cast<const QMouseEvent*>(event)->button(); + const bool rightButtonPressed = button & Qt::RightButton; + const bool shiftKeyPressed = modifiers & Qt::ShiftModifier; + const bool controlKeyPressed = modifiers & Qt::ControlModifier; + if (((index == pressedIndex && selectionModel->isSelected(index)) + || !index.isValid()) && state != QAbstractItemView::DragSelectingState + && !shiftKeyPressed && !controlKeyPressed && !rightButtonPressed) + return QItemSelectionModel::ClearAndSelect|selectionBehaviorFlags(); + return QItemSelectionModel::NoUpdate; + } + case QEvent::KeyPress: { + // NoUpdate on Key movement and Ctrl + modifiers = static_cast<const QKeyEvent*>(event)->modifiers(); + switch (static_cast<const QKeyEvent*>(event)->key()) { + case Qt::Key_Backtab: + modifiers = modifiers & ~Qt::ShiftModifier; // special case for backtab + case Qt::Key_Down: + case Qt::Key_Up: + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Home: + case Qt::Key_End: + case Qt::Key_PageUp: + case Qt::Key_PageDown: + case Qt::Key_Tab: +#ifdef QT_KEYPAD_NAVIGATION + return QItemSelectionModel::NoUpdate; +#else + if (modifiers & Qt::ControlModifier) + return QItemSelectionModel::NoUpdate; +#endif + break; + case Qt::Key_Select: + return QItemSelectionModel::Toggle|selectionBehaviorFlags(); + case Qt::Key_Space:// Toggle on Ctrl-Qt::Key_Space, Select on Space + if (modifiers & Qt::ControlModifier) + return QItemSelectionModel::Toggle|selectionBehaviorFlags(); + return QItemSelectionModel::Select|selectionBehaviorFlags(); + default: + break; + } + } + default: + break; + } + } + + if (modifiers & Qt::ShiftModifier) + return QItemSelectionModel::SelectCurrent|selectionBehaviorFlags(); + if (modifiers & Qt::ControlModifier) + return QItemSelectionModel::Toggle|selectionBehaviorFlags(); + if (state == QAbstractItemView::DragSelectingState) { + //when drag-selecting we need to clear any previous selection and select the current one + return QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent|selectionBehaviorFlags(); + } + + return QItemSelectionModel::ClearAndSelect|selectionBehaviorFlags(); +} + +QItemSelectionModel::SelectionFlags +QAbstractItemViewPrivate::contiguousSelectionCommand(const QModelIndex &index, + const QEvent *event) const +{ + QItemSelectionModel::SelectionFlags flags = extendedSelectionCommand(index, event); + const int Mask = QItemSelectionModel::Clear | QItemSelectionModel::Select + | QItemSelectionModel::Deselect | QItemSelectionModel::Toggle + | QItemSelectionModel::Current; + + switch (flags & Mask) { + case QItemSelectionModel::Clear: + case QItemSelectionModel::ClearAndSelect: + case QItemSelectionModel::SelectCurrent: + return flags; + case QItemSelectionModel::NoUpdate: + if (event && + (event->type() == QEvent::MouseButtonPress + || event->type() == QEvent::MouseButtonRelease)) + return flags; + return QItemSelectionModel::ClearAndSelect|selectionBehaviorFlags(); + default: + return QItemSelectionModel::SelectCurrent|selectionBehaviorFlags(); + } +} + +void QAbstractItemViewPrivate::_q_fetchMore() +{ + if (!model->canFetchMore(root)) + return; + int last = model->rowCount(root) - 1; + if (last < 0) { + model->fetchMore(root); + return; + } + + QModelIndex index = model->index(last, 0, root); + QRect rect = q_func()->visualRect(index); + if (viewport->rect().intersects(rect)) + model->fetchMore(root); +} + +bool QAbstractItemViewPrivate::shouldEdit(QAbstractItemView::EditTrigger trigger, + const QModelIndex &index) const +{ + if (!index.isValid()) + return false; + Qt::ItemFlags flags = model->flags(index); + if (((flags & Qt::ItemIsEditable) == 0) || ((flags & Qt::ItemIsEnabled) == 0)) + return false; + if (state == QAbstractItemView::EditingState) + return false; + if (hasEditor(index)) + return false; + if (trigger == QAbstractItemView::AllEditTriggers) // force editing + return true; + if ((trigger & editTriggers) == QAbstractItemView::SelectedClicked + && !selectionModel->isSelected(index)) + return false; + return (trigger & editTriggers); +} + +bool QAbstractItemViewPrivate::shouldForwardEvent(QAbstractItemView::EditTrigger trigger, + const QEvent *event) const +{ + if (!event || (trigger & editTriggers) != QAbstractItemView::AnyKeyPressed) + return false; + + switch (event->type()) { + case QEvent::KeyPress: + case QEvent::MouseButtonDblClick: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: + return true; + default: + break; + }; + + return false; +} + +bool QAbstractItemViewPrivate::shouldAutoScroll(const QPoint &pos) const +{ + if (!autoScroll) + return false; + QRect area = static_cast<QAbstractItemView*>(viewport)->d_func()->clipRect(); // access QWidget private by bending C++ rules + return (pos.y() - area.top() < autoScrollMargin) + || (area.bottom() - pos.y() < autoScrollMargin) + || (pos.x() - area.left() < autoScrollMargin) + || (area.right() - pos.x() < autoScrollMargin); +} + +void QAbstractItemViewPrivate::doDelayedItemsLayout(int delay) +{ + if (!delayedPendingLayout) { + delayedPendingLayout = true; + delayedLayout.start(delay, q_func()); + } +} + +void QAbstractItemViewPrivate::interruptDelayedItemsLayout() const +{ + delayedLayout.stop(); + delayedPendingLayout = false; +} + + + +QWidget *QAbstractItemViewPrivate::editor(const QModelIndex &index, + const QStyleOptionViewItem &options) +{ + Q_Q(QAbstractItemView); + QWidget *w = editorForIndex(index).editor; + if (!w) { + QAbstractItemDelegate *delegate = delegateForIndex(index); + if (!delegate) + return 0; + w = delegate->createEditor(viewport, options, index); + if (w) { + w->installEventFilter(delegate); + QObject::connect(w, SIGNAL(destroyed(QObject*)), q, SLOT(editorDestroyed(QObject*))); + delegate->updateEditorGeometry(w, options, index); + delegate->setEditorData(w, index); + addEditor(index, w, false); + if (w->parent() == viewport) + QWidget::setTabOrder(q, w); + + // Special cases for some editors containing QLineEdit + QWidget *focusWidget = w; + while (QWidget *fp = focusWidget->focusProxy()) + focusWidget = fp; +#ifndef QT_NO_LINEEDIT + if (QLineEdit *le = qobject_cast<QLineEdit*>(focusWidget)) + le->selectAll(); +#endif +#ifndef QT_NO_SPINBOX + if (QSpinBox *sb = qobject_cast<QSpinBox*>(focusWidget)) + sb->selectAll(); + else if (QDoubleSpinBox *dsb = qobject_cast<QDoubleSpinBox*>(focusWidget)) + dsb->selectAll(); +#endif + } + } + return w; +} + +void QAbstractItemViewPrivate::updateEditorData(const QModelIndex &tl, const QModelIndex &br) +{ + // we are counting on having relatively few editors + const bool checkIndexes = tl.isValid() && br.isValid(); + const QModelIndex parent = tl.parent(); + QList<QEditorInfo>::const_iterator it = editors.constBegin(); + for (; it != editors.constEnd(); ++it) { + QWidget *editor = it->editor; + const QModelIndex index = it->index; + if (it->isStatic || editor == 0 || !index.isValid() || + (checkIndexes + && (index.row() < tl.row() || index.row() > br.row() + || index.column() < tl.column() || index.column() > br.column() + || index.parent() != parent))) + continue; + + QAbstractItemDelegate *delegate = delegateForIndex(index); + if (delegate) { + delegate->setEditorData(editor, index); + } + } +} + +/*! + \internal + + In DND if something has been moved then this is called. + Typically this means you should "remove" the selected item or row, + but the behavior is view dependant (table just clears the selected indexes for example). + + Either remove the selected rows or clear them + */ +void QAbstractItemViewPrivate::clearOrRemove() +{ +#ifndef QT_NO_DRAGANDDROP + const QItemSelection selection = selectionModel->selection(); + QList<QItemSelectionRange>::const_iterator it = selection.constBegin(); + + if (!overwrite) { + for (; it != selection.constEnd(); ++it) { + QModelIndex parent = (*it).parent(); + if ((*it).left() != 0) + continue; + if ((*it).right() != (model->columnCount(parent) - 1)) + continue; + int count = (*it).bottom() - (*it).top() + 1; + model->removeRows((*it).top(), count, parent); + } + } else { + // we can't remove the rows so reset the items (i.e. the view is like a table) + QModelIndexList list = selection.indexes(); + for (int i=0; i < list.size(); ++i) { + QModelIndex index = list.at(i); + QMap<int, QVariant> roles = model->itemData(index); + for (QMap<int, QVariant>::Iterator it = roles.begin(); it != roles.end(); ++it) + it.value() = QVariant(); + model->setItemData(index, roles); + } + } +#endif +} + +/*! + \internal + + When persistent aeditor gets/loses focus, we need to check + and setcorrectly the current index. + */ +void QAbstractItemViewPrivate::checkPersistentEditorFocus() +{ + Q_Q(QAbstractItemView); + if (QWidget *widget = qApp->focusWidget()) { + if (persistent.contains(widget)) { + //a persistent editor has gained the focus + QModelIndex index = indexForEditor(widget); + if (selectionModel->currentIndex() != index) + q->setCurrentIndex(index); + } + } +} + + +QEditorInfo QAbstractItemViewPrivate::editorForIndex(const QModelIndex &index) const +{ + QList<QEditorInfo>::const_iterator it = editors.constBegin(); + for (; it != editors.constEnd(); ++it) { + if (it->index == index) + return *it; + } + + return QEditorInfo(); +} + +QModelIndex QAbstractItemViewPrivate::indexForEditor(QWidget *editor) const +{ + QList<QEditorInfo>::const_iterator it = editors.constBegin(); + for (; it != editors.constEnd(); ++it) { + if (it->editor == editor) + return it->index; + } + return QModelIndex(); +} + +void QAbstractItemViewPrivate::removeEditor(QWidget *editor) +{ + QList<QEditorInfo>::iterator it = editors.begin(); + for (; it != editors.end(); ) { + if (it->editor == editor) + it = editors.erase(it); + else + ++it; + } +} + +void QAbstractItemViewPrivate::addEditor(const QModelIndex &index, QWidget *editor, bool isStatic) +{ + editors.append(QEditorInfo(index, editor, isStatic)); +} + +bool QAbstractItemViewPrivate::sendDelegateEvent(const QModelIndex &index, QEvent *event) const +{ + Q_Q(const QAbstractItemView); + QModelIndex buddy = model->buddy(index); + QStyleOptionViewItemV4 options = viewOptionsV4(); + options.rect = q->visualRect(buddy); + options.state |= (buddy == q->currentIndex() ? QStyle::State_HasFocus : QStyle::State_None); + QAbstractItemDelegate *delegate = delegateForIndex(index); + return (event && delegate && delegate->editorEvent(event, model, options, buddy)); +} + +bool QAbstractItemViewPrivate::openEditor(const QModelIndex &index, QEvent *event) +{ + Q_Q(QAbstractItemView); + + QModelIndex buddy = model->buddy(index); + QStyleOptionViewItemV4 options = viewOptionsV4(); + options.rect = q->visualRect(buddy); + options.state |= (buddy == q->currentIndex() ? QStyle::State_HasFocus : QStyle::State_None); + + QWidget *w = editor(buddy, options); + if (!w) + return false; + + if (event) + QApplication::sendEvent(w->focusProxy() ? w->focusProxy() : w, event); + + q->setState(QAbstractItemView::EditingState); + w->show(); + w->setFocus(); + + return true; +} + +QPixmap QAbstractItemViewPrivate::renderToPixmap(const QModelIndexList &indexes, QRect *r) const +{ + Q_Q(const QAbstractItemView); + QRect rect = q->visualRect(indexes.at(0)); + QList<QRect> rects; + for (int i = 0; i < indexes.count(); ++i) { + rects.append(q->visualRect(indexes.at(i))); + rect |= rects.at(i); + } + rect = rect.intersected(viewport->rect()); + if (rect.width() <= 0 || rect.height() <= 0) + return QPixmap(); + QImage image(rect.size(), QImage::Format_ARGB32_Premultiplied); + image.fill(0); + QPainter painter(&image); + QStyleOptionViewItemV4 option = viewOptionsV4(); + option.state |= QStyle::State_Selected; + for (int j = 0; j < indexes.count(); ++j) { + option.rect = QRect(rects.at(j).topLeft() - rect.topLeft(), rects.at(j).size()); + delegateForIndex(indexes.at(j))->paint(&painter, option, indexes.at(j)); + } + painter.end(); + if (r) *r = rect; + return QPixmap::fromImage(image); +} + +void QAbstractItemViewPrivate::selectAll(QItemSelectionModel::SelectionFlags command) +{ + if (!selectionModel) + return; + + QItemSelection selection; + QModelIndex tl = model->index(0, 0, root); + QModelIndex br = model->index(model->rowCount(root) - 1, + model->columnCount(root) - 1, + root); + selection.append(QItemSelectionRange(tl, br)); + selectionModel->select(selection, command); +} + +QModelIndexList QAbstractItemViewPrivate::selectedDraggableIndexes() const +{ + Q_Q(const QAbstractItemView); + QModelIndexList indexes = q->selectedIndexes(); + for(int i = indexes.count() - 1 ; i >= 0; --i) { + if (!isIndexDragEnabled(indexes.at(i))) + indexes.removeAt(i); + } + return indexes; +} + + +QT_END_NAMESPACE + +#include "moc_qabstractitemview.cpp" + +#endif // QT_NO_ITEMVIEWS diff --git a/src/gui/itemviews/qabstractitemview.h b/src/gui/itemviews/qabstractitemview.h new file mode 100644 index 0000000..da8650d --- /dev/null +++ b/src/gui/itemviews/qabstractitemview.h @@ -0,0 +1,370 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTITEMVIEW_H +#define QABSTRACTITEMVIEW_H + +#include <QtGui/qabstractscrollarea.h> +#include <QtCore/qabstractitemmodel.h> +#include <QtGui/qitemselectionmodel.h> +#include <QtGui/qabstractitemdelegate.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_ITEMVIEWS + +class QMenu; +class QDrag; +class QEvent; +class QAbstractItemViewPrivate; + +class Q_GUI_EXPORT QAbstractItemView : public QAbstractScrollArea +{ + Q_OBJECT + Q_ENUMS(SelectionMode SelectionBehavior ScrollHint ScrollMode DragDropMode) + Q_FLAGS(EditTriggers) + Q_PROPERTY(bool autoScroll READ hasAutoScroll WRITE setAutoScroll) + Q_PROPERTY(int autoScrollMargin READ autoScrollMargin WRITE setAutoScrollMargin) + Q_PROPERTY(EditTriggers editTriggers READ editTriggers WRITE setEditTriggers) + Q_PROPERTY(bool tabKeyNavigation READ tabKeyNavigation WRITE setTabKeyNavigation) +#ifndef QT_NO_DRAGANDDROP + Q_PROPERTY(bool showDropIndicator READ showDropIndicator WRITE setDropIndicatorShown) + Q_PROPERTY(bool dragEnabled READ dragEnabled WRITE setDragEnabled) + Q_PROPERTY(bool dragDropOverwriteMode READ dragDropOverwriteMode WRITE setDragDropOverwriteMode) + Q_PROPERTY(DragDropMode dragDropMode READ dragDropMode WRITE setDragDropMode) +#endif + Q_PROPERTY(bool alternatingRowColors READ alternatingRowColors WRITE setAlternatingRowColors) + Q_PROPERTY(SelectionMode selectionMode READ selectionMode WRITE setSelectionMode) + Q_PROPERTY(SelectionBehavior selectionBehavior READ selectionBehavior WRITE setSelectionBehavior) + Q_PROPERTY(QSize iconSize READ iconSize WRITE setIconSize) + Q_PROPERTY(Qt::TextElideMode textElideMode READ textElideMode WRITE setTextElideMode) + Q_PROPERTY(ScrollMode verticalScrollMode READ verticalScrollMode WRITE setVerticalScrollMode) + Q_PROPERTY(ScrollMode horizontalScrollMode READ horizontalScrollMode WRITE setHorizontalScrollMode) + +public: + enum SelectionMode { + NoSelection, + SingleSelection, + MultiSelection, + ExtendedSelection, + ContiguousSelection + }; + + enum SelectionBehavior { + SelectItems, + SelectRows, + SelectColumns + }; + + enum ScrollHint { + EnsureVisible, + PositionAtTop, + PositionAtBottom, + PositionAtCenter + }; + + enum EditTrigger { + NoEditTriggers = 0, + CurrentChanged = 1, + DoubleClicked = 2, + SelectedClicked = 4, + EditKeyPressed = 8, + AnyKeyPressed = 16, + AllEditTriggers = 31 + }; + + Q_DECLARE_FLAGS(EditTriggers, EditTrigger) + + enum ScrollMode { + ScrollPerItem, + ScrollPerPixel + }; + + explicit QAbstractItemView(QWidget *parent = 0); + ~QAbstractItemView(); + + virtual void setModel(QAbstractItemModel *model); + QAbstractItemModel *model() const; + + virtual void setSelectionModel(QItemSelectionModel *selectionModel); + QItemSelectionModel *selectionModel() const; + + void setItemDelegate(QAbstractItemDelegate *delegate); + QAbstractItemDelegate *itemDelegate() const; + + void setSelectionMode(QAbstractItemView::SelectionMode mode); + QAbstractItemView::SelectionMode selectionMode() const; + + void setSelectionBehavior(QAbstractItemView::SelectionBehavior behavior); + QAbstractItemView::SelectionBehavior selectionBehavior() const; + + QModelIndex currentIndex() const; + QModelIndex rootIndex() const; + + void setEditTriggers(EditTriggers triggers); + EditTriggers editTriggers() const; + + void setVerticalScrollMode(ScrollMode mode); + ScrollMode verticalScrollMode() const; + + void setHorizontalScrollMode(ScrollMode mode); + ScrollMode horizontalScrollMode() const; + + void setAutoScroll(bool enable); + bool hasAutoScroll() const; + + void setAutoScrollMargin(int margin); + int autoScrollMargin() const; + + void setTabKeyNavigation(bool enable); + bool tabKeyNavigation() const; + +#ifndef QT_NO_DRAGANDDROP + void setDropIndicatorShown(bool enable); + bool showDropIndicator() const; + + void setDragEnabled(bool enable); + bool dragEnabled() const; + + void setDragDropOverwriteMode(bool overwrite); + bool dragDropOverwriteMode() const; + + enum DragDropMode { + NoDragDrop, + DragOnly, + DropOnly, + DragDrop, + InternalMove + }; + + void setDragDropMode(DragDropMode behavior); + DragDropMode dragDropMode() const; +#endif + void setAlternatingRowColors(bool enable); + bool alternatingRowColors() const; + + void setIconSize(const QSize &size); + QSize iconSize() const; + + void setTextElideMode(Qt::TextElideMode mode); + Qt::TextElideMode textElideMode() const; + + virtual void keyboardSearch(const QString &search); + + virtual QRect visualRect(const QModelIndex &index) const = 0; + virtual void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) = 0; + virtual QModelIndex indexAt(const QPoint &point) const = 0; + + QSize sizeHintForIndex(const QModelIndex &index) const; + virtual int sizeHintForRow(int row) const; + virtual int sizeHintForColumn(int column) const; + + void openPersistentEditor(const QModelIndex &index); + void closePersistentEditor(const QModelIndex &index); + + void setIndexWidget(const QModelIndex &index, QWidget *widget); + QWidget *indexWidget(const QModelIndex &index) const; + + void setItemDelegateForRow(int row, QAbstractItemDelegate *delegate); + QAbstractItemDelegate *itemDelegateForRow(int row) const; + + void setItemDelegateForColumn(int column, QAbstractItemDelegate *delegate); + QAbstractItemDelegate *itemDelegateForColumn(int column) const; + + QAbstractItemDelegate *itemDelegate(const QModelIndex &index) const; + + virtual QVariant inputMethodQuery(Qt::InputMethodQuery query) const; + +#ifdef Q_NO_USING_KEYWORD + inline void update() { QAbstractScrollArea::update(); } +#else + using QAbstractScrollArea::update; +#endif + +public Q_SLOTS: + virtual void reset(); + virtual void setRootIndex(const QModelIndex &index); + virtual void doItemsLayout(); + virtual void selectAll(); + void edit(const QModelIndex &index); + void clearSelection(); + void setCurrentIndex(const QModelIndex &index); + void scrollToTop(); + void scrollToBottom(); + void update(const QModelIndex &index); + +protected Q_SLOTS: + virtual void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + virtual void rowsInserted(const QModelIndex &parent, int start, int end); + virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + virtual void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + virtual void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); + virtual void updateEditorData(); + virtual void updateEditorGeometries(); + virtual void updateGeometries(); + virtual void verticalScrollbarAction(int action); + virtual void horizontalScrollbarAction(int action); + virtual void verticalScrollbarValueChanged(int value); + virtual void horizontalScrollbarValueChanged(int value); + virtual void closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint); + virtual void commitData(QWidget *editor); + virtual void editorDestroyed(QObject *editor); + +Q_SIGNALS: + void pressed(const QModelIndex &index); + void clicked(const QModelIndex &index); + void doubleClicked(const QModelIndex &index); + + void activated(const QModelIndex &index); + void entered(const QModelIndex &index); + void viewportEntered(); + +protected: + QAbstractItemView(QAbstractItemViewPrivate &, QWidget *parent = 0); + + void setHorizontalStepsPerItem(int steps); + int horizontalStepsPerItem() const; + void setVerticalStepsPerItem(int steps); + int verticalStepsPerItem() const; + + enum CursorAction { MoveUp, MoveDown, MoveLeft, MoveRight, + MoveHome, MoveEnd, MovePageUp, MovePageDown, + MoveNext, MovePrevious }; + virtual QModelIndex moveCursor(CursorAction cursorAction, + Qt::KeyboardModifiers modifiers) = 0; + + virtual int horizontalOffset() const = 0; + virtual int verticalOffset() const = 0; + + virtual bool isIndexHidden(const QModelIndex &index) const = 0; + + virtual void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command) = 0; + virtual QRegion visualRegionForSelection(const QItemSelection &selection) const = 0; + virtual QModelIndexList selectedIndexes() const; + + virtual bool edit(const QModelIndex &index, EditTrigger trigger, QEvent *event); + + virtual QItemSelectionModel::SelectionFlags selectionCommand(const QModelIndex &index, + const QEvent *event = 0) const; + +#ifndef QT_NO_DRAGANDDROP + virtual void startDrag(Qt::DropActions supportedActions); +#endif + + virtual QStyleOptionViewItem viewOptions() const; + + enum State { + NoState, + DraggingState, + DragSelectingState, + EditingState, + ExpandingState, + CollapsingState, + AnimatingState + }; + + State state() const; + void setState(State state); + + void scheduleDelayedItemsLayout(); + void executeDelayedItemsLayout(); + + void setDirtyRegion(const QRegion ®ion); + void scrollDirtyRegion(int dx, int dy); + QPoint dirtyRegionOffset() const; + + void startAutoScroll(); + void stopAutoScroll(); + void doAutoScroll(); + + bool focusNextPrevChild(bool next); + bool event(QEvent *event); + bool viewportEvent(QEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void mouseDoubleClickEvent(QMouseEvent *event); +#ifndef QT_NO_DRAGANDDROP + void dragEnterEvent(QDragEnterEvent *event); + void dragMoveEvent(QDragMoveEvent *event); + void dragLeaveEvent(QDragLeaveEvent *event); + void dropEvent(QDropEvent *event); +#endif + void focusInEvent(QFocusEvent *event); + void focusOutEvent(QFocusEvent *event); + void keyPressEvent(QKeyEvent *event); + void resizeEvent(QResizeEvent *event); + void timerEvent(QTimerEvent *event); + void inputMethodEvent(QInputMethodEvent *event); + +#ifndef QT_NO_DRAGANDDROP + enum DropIndicatorPosition { OnItem, AboveItem, BelowItem, OnViewport }; + DropIndicatorPosition dropIndicatorPosition() const; +#endif + +private: + Q_DECLARE_PRIVATE(QAbstractItemView) + Q_DISABLE_COPY(QAbstractItemView) + Q_PRIVATE_SLOT(d_func(), void _q_columnsAboutToBeRemoved(const QModelIndex&, int, int)) + Q_PRIVATE_SLOT(d_func(), void _q_columnsRemoved(const QModelIndex&, int, int)) + Q_PRIVATE_SLOT(d_func(), void _q_columnsInserted(const QModelIndex&, int, int)) + Q_PRIVATE_SLOT(d_func(), void _q_rowsRemoved(const QModelIndex&, int, int)) + Q_PRIVATE_SLOT(d_func(), void _q_modelDestroyed()) + Q_PRIVATE_SLOT(d_func(), void _q_layoutChanged()) + Q_PRIVATE_SLOT(d_func(), void _q_fetchMore()) + + friend class QTreeViewPrivate; // needed to compile with MSVC + friend class QAccessibleItemRow; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QAbstractItemView::EditTriggers) + +#endif // QT_NO_ITEMVIEWS + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QABSTRACTITEMVIEW_H diff --git a/src/gui/itemviews/qabstractitemview_p.h b/src/gui/itemviews/qabstractitemview_p.h new file mode 100644 index 0000000..37fe4a2 --- /dev/null +++ b/src/gui/itemviews/qabstractitemview_p.h @@ -0,0 +1,410 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTITEMVIEW_P_H +#define QABSTRACTITEMVIEW_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qabstractscrollarea_p.h" +#include "private/qabstractitemmodel_p.h" +#include "QtGui/qapplication.h" +#include "QtCore/qdatetime.h" +#include "QtGui/qevent.h" +#include "QtGui/qmime.h" +#include "QtGui/qpainter.h" +#include "QtCore/qpair.h" +#include "QtCore/qtimer.h" +#include "QtCore/qtimeline.h" +#include "QtGui/qregion.h" +#include "QtCore/qdebug.h" +#include "QtGui/qpainter.h" + +#ifndef QT_NO_ITEMVIEWS + +QT_BEGIN_NAMESPACE + +struct QEditorInfo +{ + QEditorInfo() : isStatic(false) + { + } + + QEditorInfo(const QPersistentModelIndex &i, QWidget *e, bool b) : index(i), editor(e), isStatic(b) + { + } + + QPersistentModelIndex index; + QPointer<QWidget> editor; + bool isStatic; //true when called from setIndexWidget + +}; + +class QEmptyModel : public QAbstractItemModel +{ +public: + explicit QEmptyModel(QObject *parent = 0) : QAbstractItemModel(parent) {} + QModelIndex index(int, int, const QModelIndex &) const { return QModelIndex(); } + QModelIndex parent(const QModelIndex &) const { return QModelIndex(); } + int rowCount(const QModelIndex &) const { return 0; } + int columnCount(const QModelIndex &) const { return 0; } + bool hasChildren(const QModelIndex &) const { return false; } + QVariant data(const QModelIndex &, int) const { return QVariant(); } +}; + +class Q_GUI_EXPORT QAbstractItemViewPrivate : public QAbstractScrollAreaPrivate +{ + Q_DECLARE_PUBLIC(QAbstractItemView) + +public: + QAbstractItemViewPrivate(); + virtual ~QAbstractItemViewPrivate(); + + void init(); + + void _q_rowsRemoved(const QModelIndex &parent, int start, int end); + void _q_columnsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + void _q_columnsRemoved(const QModelIndex &parent, int start, int end); + void _q_columnsInserted(const QModelIndex &parent, int start, int end); + void _q_modelDestroyed(); + void _q_layoutChanged(); + void _q_fetchMore(); + + bool shouldEdit(QAbstractItemView::EditTrigger trigger, const QModelIndex &index) const; + bool shouldForwardEvent(QAbstractItemView::EditTrigger trigger, const QEvent *event) const; + bool shouldAutoScroll(const QPoint &pos) const; + void doDelayedItemsLayout(int delay = 0); + void interruptDelayedItemsLayout() const; + + bool dropOn(QDropEvent *event, int *row, int *col, QModelIndex *index); + bool droppingOnItself(QDropEvent *event, const QModelIndex &index); + + QWidget *editor(const QModelIndex &index, const QStyleOptionViewItem &options); + bool sendDelegateEvent(const QModelIndex &index, QEvent *event) const; + bool openEditor(const QModelIndex &index, QEvent *event); + void updateEditorData(const QModelIndex &topLeft, const QModelIndex &bottomRight); + + QItemSelectionModel::SelectionFlags multiSelectionCommand(const QModelIndex &index, + const QEvent *event) const; + QItemSelectionModel::SelectionFlags extendedSelectionCommand(const QModelIndex &index, + const QEvent *event) const; + QItemSelectionModel::SelectionFlags contiguousSelectionCommand(const QModelIndex &index, + const QEvent *event) const; + virtual void selectAll(QItemSelectionModel::SelectionFlags command); + + inline QItemSelectionModel::SelectionFlags selectionBehaviorFlags() const + { + switch (selectionBehavior) { + case QAbstractItemView::SelectRows: return QItemSelectionModel::Rows; + case QAbstractItemView::SelectColumns: return QItemSelectionModel::Columns; + case QAbstractItemView::SelectItems: default: return QItemSelectionModel::NoUpdate; + } + } + +#ifndef QT_NO_DRAGANDDROP + QAbstractItemView::DropIndicatorPosition position(const QPoint &pos, const QRect &rect, const QModelIndex &idx) const; + inline bool canDecode(QDropEvent *e) const { + QStringList modelTypes = model->mimeTypes(); + const QMimeData *mime = e->mimeData(); + for (int i = 0; i < modelTypes.count(); ++i) + if (mime->hasFormat(modelTypes.at(i)) + && (e->dropAction() & model->supportedDropActions())) + return true; + return false; + } + + inline void paintDropIndicator(QPainter *painter) + { + if (showDropIndicator && state == QAbstractItemView::DraggingState +#ifndef QT_NO_CURSOR + && viewport->cursor().shape() != Qt::ForbiddenCursor +#endif + ) { + QStyleOption opt; + opt.init(q_func()); + opt.rect = dropIndicatorRect; + q_func()->style()->drawPrimitive(QStyle::PE_IndicatorItemViewItemDrop, &opt, painter, q_func()); + } + } +#endif + + inline void releaseEditor(QWidget *editor) const { + if (editor) { + QObject::disconnect(editor, SIGNAL(destroyed(QObject*)), + q_func(), SLOT(editorDestroyed(QObject*))); + editor->removeEventFilter(itemDelegate); + editor->hide(); + editor->deleteLater(); + } + } + + inline void executePostedLayout() const { + if (delayedPendingLayout && state != QAbstractItemView::CollapsingState) { + interruptDelayedItemsLayout(); + const_cast<QAbstractItemView*>(q_func())->doItemsLayout(); + } + } + + inline void setDirtyRegion(const QRegion &visualRegion) { + updateRegion += visualRegion; + if (!updateTimer.isActive()) + updateTimer.start(0, q_func()); + } + + inline void scrollDirtyRegion(int dx, int dy) { + scrollDelayOffset = QPoint(-dx, -dy); + updateDirtyRegion(); + scrollDelayOffset = QPoint(0, 0); + } + + inline void scrollContentsBy(int dx, int dy) { + scrollDirtyRegion(dx, dy); + viewport->scroll(dx, dy); + } + + void updateDirtyRegion() { + updateTimer.stop(); + viewport->update(updateRegion); + updateRegion = QRegion(); + } + + void clearOrRemove(); + void checkPersistentEditorFocus(); + + QPixmap renderToPixmap(const QModelIndexList &indexes, QRect *r = 0) const; + + inline QPoint offset() const { + const Q_Q(QAbstractItemView); + return QPoint(q->isRightToLeft() ? -q->horizontalOffset() + : q->horizontalOffset(), q->verticalOffset()); + } + + QEditorInfo editorForIndex(const QModelIndex &index) const; + inline bool hasEditor(const QModelIndex &index) const { + return editorForIndex(index).editor != 0; + } + + QModelIndex indexForEditor(QWidget *editor) const; + void addEditor(const QModelIndex &index, QWidget *editor, bool isStatic); + void removeEditor(QWidget *editor); + + inline bool isAnimating() const { + return state == QAbstractItemView::AnimatingState; + } + + inline QAbstractItemDelegate *delegateForIndex(const QModelIndex &index) const { + QAbstractItemDelegate *del; + if ((del = rowDelegates.value(index.row(), 0))) return del; + if ((del = columnDelegates.value(index.column(), 0))) return del; + return itemDelegate; + } + + inline bool isIndexValid(const QModelIndex &index) const { + return (index.row() >= 0) && (index.column() >= 0) && (index.model() == model); + } + inline bool isIndexSelectable(const QModelIndex &index) const { + return (model->flags(index) & Qt::ItemIsSelectable); + } + inline bool isIndexEnabled(const QModelIndex &index) const { + return (model->flags(index) & Qt::ItemIsEnabled); + } + inline bool isIndexDropEnabled(const QModelIndex &index) const { + return (model->flags(index) & Qt::ItemIsDropEnabled); + } + inline bool isIndexDragEnabled(const QModelIndex &index) const { + return (model->flags(index) & Qt::ItemIsDragEnabled); + } + + virtual bool selectionAllowed(const QModelIndex &index) const { + // in some views we want to go ahead with selections, even if the index is invalid + return isIndexValid(index) && isIndexSelectable(index); + } + + // reimplemented from QAbstractScrollAreaPrivate + virtual QPoint contentsOffset() const { + Q_Q(const QAbstractItemView); + return QPoint(q->horizontalOffset(), q->verticalOffset()); + } + + /** + * For now, assume that we have few editors, if we need a more efficient implementation + * we should add a QMap<QAbstractItemDelegate*, int> member. + */ + int delegateRefCount(const QAbstractItemDelegate *delegate) const + { + int ref = 0; + if (itemDelegate == delegate) + ++ref; + + for (int maps = 0; maps < 2; ++maps) { + const QMap<int, QPointer<QAbstractItemDelegate> > *delegates = maps ? &columnDelegates : &rowDelegates; + for (QMap<int, QPointer<QAbstractItemDelegate> >::const_iterator it = delegates->begin(); + it != delegates->end(); ++it) { + if (it.value() == delegate) { + ++ref; + // optimization, we are only interested in the ref count values 0, 1 or >=2 + if (ref >= 2) { + return ref; + } + } + } + } + return ref; + } + + /** + * return true if the index is registered as a QPersistentModelIndex + */ + inline bool isPersistent(const QModelIndex &index) const + { + return static_cast<QAbstractItemModelPrivate *>(model->d_ptr)->persistent.indexes.contains(index); + } + + QModelIndexList selectedDraggableIndexes() const; + + QStyleOptionViewItemV4 viewOptionsV4() const; + + QAbstractItemModel *model; + QPointer<QAbstractItemDelegate> itemDelegate; + QMap<int, QPointer<QAbstractItemDelegate> > rowDelegates; + QMap<int, QPointer<QAbstractItemDelegate> > columnDelegates; + QPointer<QItemSelectionModel> selectionModel; + + QAbstractItemView::SelectionMode selectionMode; + QAbstractItemView::SelectionBehavior selectionBehavior; + + QList<QEditorInfo> editors; + QSet<QWidget*> persistent; + QWidget *currentlyCommittingEditor; + + QPersistentModelIndex enteredIndex; + QPersistentModelIndex pressedIndex; + Qt::KeyboardModifiers pressedModifiers; + QPoint pressedPosition; + bool pressedAlreadySelected; + + //forces the next mouseMoveEvent to send the viewportEntered signal + //if the mouse is over the viewport and not over an item + bool viewportEnteredNeeded; + + QAbstractItemView::State state; + QAbstractItemView::EditTriggers editTriggers; + QAbstractItemView::EditTrigger lastTrigger; + + QPersistentModelIndex root; + QPersistentModelIndex hover; + + bool tabKeyNavigation; + +#ifndef QT_NO_DRAGANDDROP + bool showDropIndicator; + QRect dropIndicatorRect; + bool dragEnabled; + QAbstractItemView::DragDropMode dragDropMode; + bool overwrite; + QAbstractItemView::DropIndicatorPosition dropIndicatorPosition; +#endif + + QString keyboardInput; + QTime keyboardInputTime; + + bool autoScroll; + QBasicTimer autoScrollTimer; + int autoScrollMargin; + int autoScrollCount; + + bool alternatingColors; + + QSize iconSize; + Qt::TextElideMode textElideMode; + + QRegion updateRegion; // used for the internal update system + QPoint scrollDelayOffset; + + QBasicTimer updateTimer; + QBasicTimer delayedEditing; + QBasicTimer delayedAutoScroll; //used when an item is clicked + QTimeLine timeline; + + QAbstractItemView::ScrollMode verticalScrollMode; + QAbstractItemView::ScrollMode horizontalScrollMode; + + bool currentIndexSet; + + bool wrapItemText; + mutable bool delayedPendingLayout; + +private: + mutable QBasicTimer delayedLayout; +}; + +QT_BEGIN_INCLUDE_NAMESPACE +#include <qvector.h> +QT_END_INCLUDE_NAMESPACE + +template <typename T> +inline int qBinarySearch(const QVector<T> &vec, const T &item, int start, int end) +{ + int i = (start + end + 1) >> 1; + while (end - start > 0) { + if (vec.at(i) > item) + end = i - 1; + else + start = i; + i = (start + end + 1) >> 1; + } + return i; +} + +QT_END_NAMESPACE + +#endif // QT_NO_ITEMVIEWS + +#endif // QABSTRACTITEMVIEW_P_H diff --git a/src/gui/itemviews/qabstractproxymodel.cpp b/src/gui/itemviews/qabstractproxymodel.cpp new file mode 100644 index 0000000..d6e3a93 --- /dev/null +++ b/src/gui/itemviews/qabstractproxymodel.cpp @@ -0,0 +1,282 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qabstractproxymodel.h" + +#ifndef QT_NO_PROXYMODEL + +#include "qitemselectionmodel.h" +#include <private/qabstractproxymodel_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \since 4.1 + \class QAbstractProxyModel + \brief The QAbstractProxyModel class provides a base class for proxy item + models that can do sorting, filtering or other data processing tasks. + \ingroup model-view + + This class defines the standard interface that proxy models must use to be + able to interoperate correctly with other model/view components. It is not + supposed to be instantiated directly. + + All standard proxy models are derived from the QAbstractProxyModel class. + If you need to create a new proxy model class, it is usually better to + subclass an existing class that provides the closest behavior to the one + you want to provide. + + Proxy models that filter or sort items of data from a source model should + be created by using or subclassing QSortFilterProxyModel. + + To subclass QAbstractProxyModel, you need to implement mapFromSource() and + mapToSource(). The mapSelectionFromSource() and mapSelectionToSource() + functions only need to be reimplemented if you need a behavior different + from the default behavior. + + \note If the source model is deleted or no source model is specified, the + proxy model operates on a empty placeholder model. + + \sa QSortFilterProxyModel, QAbstractItemModel, {Model/View Programming} +*/ + +//detects the deletion of the source model +void QAbstractProxyModelPrivate::_q_sourceModelDestroyed() +{ + model = QAbstractItemModelPrivate::staticEmptyModel(); +} + +/*! + Constructs a proxy model with the given \a parent. +*/ + +QAbstractProxyModel::QAbstractProxyModel(QObject *parent) + :QAbstractItemModel(*new QAbstractProxyModelPrivate, parent) +{ + setSourceModel(QAbstractItemModelPrivate::staticEmptyModel()); +} + +/*! + \internal +*/ + +QAbstractProxyModel::QAbstractProxyModel(QAbstractProxyModelPrivate &dd, QObject *parent) + : QAbstractItemModel(dd, parent) +{ + setSourceModel(QAbstractItemModelPrivate::staticEmptyModel()); +} + +/*! + Destroys the proxy model. +*/ +QAbstractProxyModel::~QAbstractProxyModel() +{ + +} + +/*! + Sets the given \a sourceModel to be processed by the proxy model. +*/ +void QAbstractProxyModel::setSourceModel(QAbstractItemModel *sourceModel) +{ + Q_D(QAbstractProxyModel); + if (d->model) + disconnect(d->model, SIGNAL(destroyed()), this, SLOT(_q_sourceModelDestroyed())); + + if (sourceModel) { + d->model = sourceModel; + connect(d->model, SIGNAL(destroyed()), this, SLOT(_q_sourceModelDestroyed())); + } else { + d->model = QAbstractItemModelPrivate::staticEmptyModel(); + } +} + +/*! + Returns the model that contains the data that is available through the proxy model. +*/ +QAbstractItemModel *QAbstractProxyModel::sourceModel() const +{ + Q_D(const QAbstractProxyModel); + if (d->model == QAbstractItemModelPrivate::staticEmptyModel()) + return 0; + return d->model; +} + +/*! + \reimp + */ +bool QAbstractProxyModel::submit() +{ + Q_D(QAbstractProxyModel); + return d->model->submit(); +} + +/*! + \reimp + */ +void QAbstractProxyModel::revert() +{ + Q_D(QAbstractProxyModel); + d->model->revert(); +} + + +/*! + \fn QModelIndex QAbstractProxyModel::mapToSource(const QModelIndex &proxyIndex) const + + Reimplement this function to return the model index in the source model that + corresponds to the \a proxyIndex in the proxy model. + + \sa mapFromSource() +*/ + +/*! + \fn QModelIndex QAbstractProxyModel::mapFromSource(const QModelIndex &sourceIndex) const + + Reimplement this function to return the model index in the proxy model that + corresponds to the \a sourceIndex from the source model. + + \sa mapToSource() +*/ + +/*! + Returns a source selection mapped from the specified \a proxySelection. + + Reimplement this method to map proxy selections to source selections. + */ +QItemSelection QAbstractProxyModel::mapSelectionToSource(const QItemSelection &proxySelection) const +{ + QModelIndexList proxyIndexes = proxySelection.indexes(); + QItemSelection sourceSelection; + for (int i = 0; i < proxyIndexes.size(); ++i) + sourceSelection << QItemSelectionRange(mapToSource(proxyIndexes.at(i))); + return sourceSelection; +} + +/*! + Returns a proxy selection mapped from the specified \a sourceSelection. + + Reimplement this method to map source selections to proxy selections. +*/ +QItemSelection QAbstractProxyModel::mapSelectionFromSource(const QItemSelection &sourceSelection) const +{ + QModelIndexList sourceIndexes = sourceSelection.indexes(); + QItemSelection proxySelection; + for (int i = 0; i < sourceIndexes.size(); ++i) + proxySelection << QItemSelectionRange(mapFromSource(sourceIndexes.at(i))); + return proxySelection; +} + +/*! + \reimp + */ +QVariant QAbstractProxyModel::data(const QModelIndex &proxyIndex, int role) const +{ + Q_D(const QAbstractProxyModel); + return d->model->data(mapToSource(proxyIndex), role); +} + +/*! + \reimp + */ +QVariant QAbstractProxyModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_D(const QAbstractProxyModel); + int sourceSection; + if (orientation == Qt::Horizontal) { + const QModelIndex proxyIndex = index(0, section); + sourceSection = mapToSource(proxyIndex).column(); + } else { + const QModelIndex proxyIndex = index(section, 0); + sourceSection = mapToSource(proxyIndex).row(); + } + return d->model->headerData(sourceSection, orientation, role); +} + +/*! + \reimp + */ +QMap<int, QVariant> QAbstractProxyModel::itemData(const QModelIndex &proxyIndex) const +{ + Q_D(const QAbstractProxyModel); + return d->model->itemData(mapToSource(proxyIndex)); +} + +/*! + \reimp + */ +Qt::ItemFlags QAbstractProxyModel::flags(const QModelIndex &index) const +{ + Q_D(const QAbstractProxyModel); + return d->model->flags(mapToSource(index)); +} + +/*! + \reimp + */ +bool QAbstractProxyModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + Q_D(QAbstractProxyModel); + return d->model->setData(mapToSource(index), value, role); +} + +/*! + \reimp + */ +bool QAbstractProxyModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) +{ + Q_D(QAbstractProxyModel); + int sourceSection; + if (orientation == Qt::Horizontal) { + const QModelIndex proxyIndex = index(0, section); + sourceSection = mapToSource(proxyIndex).column(); + } else { + const QModelIndex proxyIndex = index(section, 0); + sourceSection = mapToSource(proxyIndex).row(); + } + return d->model->setHeaderData(sourceSection, orientation, value, role); +} + +QT_END_NAMESPACE + +#include "moc_qabstractproxymodel.cpp" + +#endif // QT_NO_PROXYMODEL diff --git a/src/gui/itemviews/qabstractproxymodel.h b/src/gui/itemviews/qabstractproxymodel.h new file mode 100644 index 0000000..51c8829 --- /dev/null +++ b/src/gui/itemviews/qabstractproxymodel.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTPROXYMODEL_H +#define QABSTRACTPROXYMODEL_H + +#include <QtCore/qabstractitemmodel.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_PROXYMODEL + +class QAbstractProxyModelPrivate; +class QItemSelection; + +class Q_GUI_EXPORT QAbstractProxyModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + QAbstractProxyModel(QObject *parent = 0); + ~QAbstractProxyModel(); + + virtual void setSourceModel(QAbstractItemModel *sourceModel); + QAbstractItemModel *sourceModel() const; + + virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const = 0; + virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const = 0; + + virtual QItemSelection mapSelectionToSource(const QItemSelection &selection) const; + virtual QItemSelection mapSelectionFromSource(const QItemSelection &selection) const; + + bool submit(); + void revert(); + + QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + QMap<int, QVariant> itemData(const QModelIndex &index) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole); + +protected: + QAbstractProxyModel(QAbstractProxyModelPrivate &, QObject *parent); + +private: + Q_DECLARE_PRIVATE(QAbstractProxyModel) + Q_DISABLE_COPY(QAbstractProxyModel) + Q_PRIVATE_SLOT(d_func(), void _q_sourceModelDestroyed()) +}; + +#endif // QT_NO_PROXYMODEL + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QABSTRACTPROXYMODEL_H diff --git a/src/gui/itemviews/qabstractproxymodel_p.h b/src/gui/itemviews/qabstractproxymodel_p.h new file mode 100644 index 0000000..a319953 --- /dev/null +++ b/src/gui/itemviews/qabstractproxymodel_p.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTPROXYMODEL_P_H +#define QABSTRACTPROXYMODEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of QAbstractItemModel*. This header file may change from version +// to version without notice, or even be removed. +// +// We mean it. +// +// + +#include "private/qabstractitemmodel_p.h" + +#ifndef QT_NO_PROXYMODEL + +QT_BEGIN_NAMESPACE + +class QAbstractProxyModelPrivate : public QAbstractItemModelPrivate +{ + Q_DECLARE_PUBLIC(QAbstractProxyModel) +public: + QAbstractProxyModelPrivate() : QAbstractItemModelPrivate(), model(0) {} + QAbstractItemModel *model; + virtual void _q_sourceModelDestroyed(); +}; + +QT_END_NAMESPACE + +#endif // QT_NO_PROXYMODEL + +#endif // QABSTRACTPROXYMODEL_P_H diff --git a/src/gui/itemviews/qbsptree.cpp b/src/gui/itemviews/qbsptree.cpp new file mode 100644 index 0000000..a09df53 --- /dev/null +++ b/src/gui/itemviews/qbsptree.cpp @@ -0,0 +1,145 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbsptree_p.h" + +QT_BEGIN_NAMESPACE + +QBspTree::QBspTree() : depth(6), visited(0) {} + +void QBspTree::create(int n, int d) +{ + // simple heuristics to find the best tree depth + if (d == -1) { + int c; + for (c = 0; n; ++c) + n = n / 10; + depth = c << 1; + } else { + depth = d; + } + depth = qMax(depth, uint(1)); + + nodes.resize((1 << depth) - 1); // resize to number of nodes + leaves.resize(1 << depth); // resize to number of leaves +} + +void QBspTree::destroy() +{ + leaves.clear(); + nodes.clear(); +} + +void QBspTree::climbTree(const QRect &rect, callback *function, QBspTreeData data) +{ + if (nodes.isEmpty()) + return; + ++visited; + climbTree(rect, function, data, 0); +} + +void QBspTree::climbTree(const QRect &area, callback *function, QBspTreeData data, int index) +{ + if (index >= nodes.count()) { // the index points to a leaf + Q_ASSERT(!nodes.isEmpty()); + function(leaf(index - nodes.count()), area, visited, data); + return; + } + + Node::Type t = (Node::Type) nodes.at(index).type; + + int pos = nodes.at(index).pos; + int idx = firstChildIndex(index); + if (t == Node::VerticalPlane) { + if (area.left() < pos) + climbTree(area, function, data, idx); // back + if (area.right() >= pos) + climbTree(area, function, data, idx + 1); // front + } else { + if (area.top() < pos) + climbTree(area, function, data, idx); // back + if (area.bottom() >= pos) + climbTree(area, function, data, idx + 1); // front + } +} + +void QBspTree::init(const QRect &area, int depth, NodeType type, int index) +{ + Node::Type t = Node::None; // t should never have this value + if (type == Node::Both) // if both planes are specified, use 2d bsp + t = (depth & 1) ? Node::HorizontalPlane : Node::VerticalPlane; + else + t = type; + QPoint center = area.center(); + nodes[index].pos = (t == Node::VerticalPlane ? center.x() : center.y()); + nodes[index].type = t; + + QRect front = area; + QRect back = area; + + if (t == Node::VerticalPlane) { + front.setLeft(center.x()); + back.setRight(center.x() - 1); // front includes the center + } else { // t == Node::HorizontalPlane + front.setTop(center.y()); + back.setBottom(center.y() - 1); + } + + int idx = firstChildIndex(index); + if (--depth) { + init(back, depth, type, idx); + init(front, depth, type, idx + 1); + } +} + +void QBspTree::insert(QVector<int> &leaf, const QRect &, uint, QBspTreeData data) +{ + leaf.append(data.i); +} + +void QBspTree::remove(QVector<int> &leaf, const QRect &, uint, QBspTreeData data) +{ + int i = leaf.indexOf(data.i); + if (i != -1) + leaf.remove(i); +} + +QT_END_NAMESPACE diff --git a/src/gui/itemviews/qbsptree_p.h b/src/gui/itemviews/qbsptree_p.h new file mode 100644 index 0000000..ff91bfb --- /dev/null +++ b/src/gui/itemviews/qbsptree_p.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBSPTREE_P_H +#define QBSPTREE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <qvector.h> +#include <qrect.h> + +QT_BEGIN_NAMESPACE + +class QBspTree +{ +public: + + struct Node + { + enum Type { None = 0, VerticalPlane = 1, HorizontalPlane = 2, Both = 3 }; + inline Node() : pos(0), type(None) {} + int pos; + Type type; + }; + typedef Node::Type NodeType; + + struct Data + { + Data(void *p) : ptr(p) {} + Data(int n) : i(n) {} + union { + void *ptr; + int i; + }; + }; + typedef QBspTree::Data QBspTreeData; + typedef void callback(QVector<int> &leaf, const QRect &area, uint visited, QBspTreeData data); + + QBspTree(); + + void create(int n, int d = -1); + void destroy(); + + inline void init(const QRect &area, NodeType type) { init(area, depth, type, 0); } + + void climbTree(const QRect &rect, callback *function, QBspTreeData data); + + inline int leafCount() const { return leaves.count(); } + inline QVector<int> &leaf(int i) { return leaves[i]; } + inline void insertLeaf(const QRect &r, int i) { climbTree(r, &insert, i, 0); } + inline void removeLeaf(const QRect &r, int i) { climbTree(r, &remove, i, 0); } + +protected: + void init(const QRect &area, int depth, NodeType type, int index); + void climbTree(const QRect &rect, callback *function, QBspTreeData data, int index); + + inline int parentIndex(int i) const { return (i & 1) ? ((i - 1) / 2) : ((i - 2) / 2); } + inline int firstChildIndex(int i) const { return ((i * 2) + 1); } + + static void insert(QVector<int> &leaf, const QRect &area, uint visited, QBspTreeData data); + static void remove(QVector<int> &leaf, const QRect &area, uint visited, QBspTreeData data); + +private: + uint depth; + mutable uint visited; + QVector<Node> nodes; + mutable QVector< QVector<int> > leaves; // the leaves are just indices into the items +}; + +QT_END_NAMESPACE + +#endif // QBSPTREE_P_H diff --git a/src/gui/itemviews/qcolumnview.cpp b/src/gui/itemviews/qcolumnview.cpp new file mode 100644 index 0000000..4fb08bb --- /dev/null +++ b/src/gui/itemviews/qcolumnview.cpp @@ -0,0 +1,1128 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qglobal.h> + +#ifndef QT_NO_COLUMNVIEW + +#include "qcolumnview.h" +#include "qcolumnview_p.h" +#include "qcolumnviewgrip_p.h" + +#include <qlistview.h> +#include <qabstractitemdelegate.h> +#include <qscrollbar.h> +#include <qpainter.h> +#include <qdebug.h> +#include <qpainterpath.h> + +QT_BEGIN_NAMESPACE + +#define ANIMATION_DURATION_MSEC 150 + +/*! + \since 4.3 + \class QColumnView + \brief The QColumnView class provides a model/view implementation of a column view. + \ingroup model-view + \ingroup advanced + \mainclass + + QColumnView displays a model in a number of QListViews, one for each + hierarchy in the tree. This is sometimes referred to as a cascading list. + + The QColumnView class is one of the \l{Model/View Classes} + and is part of Qt's \l{Model/View Programming}{model/view framework}. + + QColumnView implements the interfaces defined by the + QAbstractItemView class to allow it to display data provided by + models derived from the QAbstractItemModel class. + + \image qcolumnview.png + + \sa \link model-view-programming.html Model/View Programming\endlink +*/ + +/*! + Constructs a column view with a \a parent to represent a model's + data. Use setModel() to set the model. + + \sa QAbstractItemModel +*/ +QColumnView::QColumnView(QWidget * parent) +: QAbstractItemView(*new QColumnViewPrivate, parent) +{ + Q_D(QColumnView); + d->initialize(); +} + +/*! + \internal +*/ +QColumnView::QColumnView(QColumnViewPrivate & dd, QWidget * parent) +: QAbstractItemView(dd, parent) +{ + Q_D(QColumnView); + d->initialize(); +} + +void QColumnViewPrivate::initialize() +{ + Q_Q(QColumnView); + q->setTextElideMode(Qt::ElideMiddle); + q->connect(¤tAnimation, SIGNAL(frameChanged(int)), + q->horizontalScrollBar(), SLOT(setValue(int))); + q->connect(¤tAnimation, SIGNAL(finished()), q, SLOT(_q_changeCurrentColumn())); + delete itemDelegate; + q->setItemDelegate(new QColumnViewDelegate(q)); +} + +/*! + Destroys the column view. +*/ +QColumnView::~QColumnView() +{ +} + +/*! + \property QColumnView::resizeGripsVisible + \brief the way to specify if the list views gets resize grips or not + + By default, \c visible is set to true + + \sa setRootIndex() +*/ +void QColumnView::setResizeGripsVisible(bool visible) +{ + Q_D(QColumnView); + if (d->showResizeGrips == visible) + return; + d->showResizeGrips = visible; + for (int i = 0; i < d->columns.count(); ++i) { + QAbstractItemView *view = d->columns[i]; + if (visible) { + QColumnViewGrip *grip = new QColumnViewGrip(view); + view->setCornerWidget(grip); + connect(grip, SIGNAL(gripMoved(int)), this, SLOT(_q_gripMoved(int))); + } else { + QWidget *widget = view->cornerWidget(); + view->setCornerWidget(0); + widget->deleteLater(); + } + } +} + +bool QColumnView::resizeGripsVisible() const +{ + Q_D(const QColumnView); + return d->showResizeGrips; +} + +/*! + \reimp +*/ +void QColumnView::setModel(QAbstractItemModel *model) +{ + Q_D(QColumnView); + if (model == d->model) + return; + d->closeColumns(); + QAbstractItemView::setModel(model); +} + +/*! + \reimp +*/ +void QColumnView::setRootIndex(const QModelIndex &index) +{ + Q_D(QColumnView); + if (!model()) + return; + + d->closeColumns(); + Q_ASSERT(d->columns.count() == 0); + + QAbstractItemView *view = d->createColumn(index, true); + if (view->selectionModel()) + view->selectionModel()->deleteLater(); + if (view->model()) + view->setSelectionModel(selectionModel()); + + QAbstractItemView::setRootIndex(index); + d->updateScrollbars(); +} + +/*! + \reimp +*/ +bool QColumnView::isIndexHidden(const QModelIndex &index) const +{ + Q_UNUSED(index); + return false; +} + +/*! + \reimp +*/ +QModelIndex QColumnView::indexAt(const QPoint &point) const +{ + Q_D(const QColumnView); + for (int i = 0; i < d->columns.size(); ++i) { + QPoint topLeft = d->columns.at(i)->frameGeometry().topLeft(); + QPoint adjustedPoint(point.x() - topLeft.x(), point.y() - topLeft.y()); + QModelIndex index = d->columns.at(i)->indexAt(adjustedPoint); + if (index.isValid()) + return index; + } + return QModelIndex(); +} + +/*! + \reimp +*/ +QRect QColumnView::visualRect(const QModelIndex &index) const +{ + if (!index.isValid()) + return QRect(); + + Q_D(const QColumnView); + for (int i = 0; i < d->columns.size(); ++i) { + QRect rect = d->columns.at(i)->visualRect(index); + if (!rect.isNull()) { + rect.translate(d->columns.at(i)->frameGeometry().topLeft()); + return rect; + } + } + return QRect(); +} + +/*! + \reimp + */ +void QColumnView::scrollContentsBy(int dx, int dy) +{ + Q_D(QColumnView); + if (d->columns.isEmpty() || dx == 0) + return; + + dx = isRightToLeft() ? -dx : dx; + for (int i = 0; i < d->columns.count(); ++i) + d->columns.at(i)->move(d->columns.at(i)->x() + dx, 0); + d->offset += dx; + QAbstractItemView::scrollContentsBy(dx, dy); +} + +/*! + \reimp +*/ +void QColumnView::scrollTo(const QModelIndex &index, ScrollHint hint) +{ + Q_D(QColumnView); + Q_UNUSED(hint); + if (!index.isValid() || d->columns.isEmpty()) + return; + + if (d->currentAnimation.state() == QTimeLine::Running) + return; + + d->currentAnimation.stop(); + + // Fill up what is needed to get to index + d->closeColumns(index, true); + + QModelIndex indexParent = index.parent(); + // Find the left edge of the column that contains index + int currentColumn = 0; + int leftEdge = 0; + while (currentColumn < d->columns.size()) { + if (indexParent == d->columns.at(currentColumn)->rootIndex()) + break; + leftEdge += d->columns.at(currentColumn)->width(); + ++currentColumn; + } + + // Don't let us scroll above the root index + if (currentColumn == d->columns.size()) + return; + + int indexColumn = currentColumn; + // Find the width of what we want to show (i.e. the right edge) + int visibleWidth = d->columns.at(currentColumn)->width(); + // We want to always try to show two columns + if (currentColumn + 1 < d->columns.size()) { + ++currentColumn; + visibleWidth += d->columns.at(currentColumn)->width(); + } + + int rightEdge = leftEdge + visibleWidth; + if (isRightToLeft()) { + leftEdge = viewport()->width() - leftEdge; + rightEdge = leftEdge - visibleWidth; + qSwap(rightEdge, leftEdge); + } + + // If it is already visible don't animate + if (leftEdge > -horizontalOffset() + && rightEdge <= ( -horizontalOffset() + viewport()->size().width())) { + d->columns.at(indexColumn)->scrollTo(index); + d->_q_changeCurrentColumn(); + return; + } + + int newScrollbarValue = 0; + if (isRightToLeft()) { + if (leftEdge < 0) { + // scroll to the right + newScrollbarValue = viewport()->size().width() - leftEdge; + } else { + // scroll to the left + newScrollbarValue = rightEdge + horizontalOffset(); + } + } else { + if (leftEdge > -horizontalOffset()) { + // scroll to the right + newScrollbarValue = rightEdge - viewport()->size().width(); + } else { + // scroll to the left + newScrollbarValue = leftEdge; + } + } + + //horizontalScrollBar()->setValue(newScrollbarValue); + //d->_q_changeCurrentColumn(); + //return; + // or do the following currentAnimation + + int oldValue = horizontalScrollBar()->value(); + + if (oldValue < newScrollbarValue) { + d->currentAnimation.setFrameRange(oldValue, newScrollbarValue); + d->currentAnimation.setDirection(QTimeLine::Forward); + d->currentAnimation.setCurrentTime(0); + } else { + d->currentAnimation.setFrameRange(newScrollbarValue, oldValue); + d->currentAnimation.setDirection(QTimeLine::Backward); + } + d->currentAnimation.start(); +} + +/*! + \reimp + Move left should go to the parent index + Move right should go to the child index or down if there is no child +*/ +QModelIndex QColumnView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) +{ + // the child views which have focus get to deal with this first and if + // they don't accept it then it comes up this view and we only grip left/right + Q_UNUSED(modifiers); + if (!model()) + return QModelIndex(); + + QModelIndex current = currentIndex(); + if (isRightToLeft()) { + if (cursorAction == MoveLeft) + cursorAction = MoveRight; + else if (cursorAction == MoveRight) + cursorAction = MoveLeft; + } + switch (cursorAction) { + case MoveLeft: + if (current.parent().isValid() && current.parent() != rootIndex()) + return (current.parent()); + else + return current; + break; + + case MoveRight: + if (model()->hasChildren(current)) + return model()->index(0, 0, current); + else + return current.sibling(current.row() + 1, current.column()); + break; + + default: + break; + } + + return QModelIndex(); +} + +/*! + \reimp +*/ +void QColumnView::resizeEvent(QResizeEvent *event) +{ + Q_D(QColumnView); + d->doLayout(); + d->updateScrollbars(); + if (!isRightToLeft()) { + int diff = event->oldSize().width() - event->size().width(); + if (diff < 0 && horizontalScrollBar()->isVisible() + && horizontalScrollBar()->value() == horizontalScrollBar()->maximum()) { + horizontalScrollBar()->setMaximum(horizontalScrollBar()->maximum() + diff); + } + } + QAbstractItemView::resizeEvent(event); +} + +/*! + \internal +*/ +void QColumnViewPrivate::updateScrollbars() +{ + Q_Q(QColumnView); + if (currentAnimation.state() == QTimeLine::Running) + return; + + // find the total horizontal length of the laid out columns + int horizontalLength = 0; + if (!columns.isEmpty()) { + horizontalLength = (columns.last()->x() + columns.last()->width()) - columns.first()->x(); + if (horizontalLength <= 0) // reverse mode + horizontalLength = (columns.first()->x() + columns.first()->width()) - columns.last()->x(); + } + + QSize viewportSize = q->viewport()->size(); + if (horizontalLength < viewportSize.width() && q->horizontalScrollBar()->value() == 0) { + q->horizontalScrollBar()->setRange(0, 0); + } else { + int visibleLength = qMin(horizontalLength + q->horizontalOffset(), viewportSize.width()); + int hiddenLength = horizontalLength - visibleLength; + if (hiddenLength != q->horizontalScrollBar()->maximum()) + q->horizontalScrollBar()->setRange(0, hiddenLength); + } + if (!columns.isEmpty()) { + int pageStepSize = columns.at(0)->width(); + if (pageStepSize != q->horizontalScrollBar()->pageStep()) + q->horizontalScrollBar()->setPageStep(pageStepSize); + } + bool visible = (q->horizontalScrollBar()->maximum() > 0); + if (visible != q->horizontalScrollBar()->isVisible()) + q->horizontalScrollBar()->setVisible(visible); +} + +/*! + \reimp +*/ +int QColumnView::horizontalOffset() const +{ + Q_D(const QColumnView); + return d->offset; +} + +/*! + \reimp +*/ +int QColumnView::verticalOffset() const +{ + return 0; +} + +/*! + \reimp +*/ +QRegion QColumnView::visualRegionForSelection(const QItemSelection &selection) const +{ + int ranges = selection.count(); + + if (ranges == 0) + return QRect(); + + // Note that we use the top and bottom functions of the selection range + // since the data is stored in rows. + int firstRow = selection.at(0).top(); + int lastRow = selection.at(0).top(); + for (int i = 0; i < ranges; ++i) { + firstRow = qMin(firstRow, selection.at(i).top()); + lastRow = qMax(lastRow, selection.at(i).bottom()); + } + + QModelIndex firstIdx = model()->index(qMin(firstRow, lastRow), 0, rootIndex()); + QModelIndex lastIdx = model()->index(qMax(firstRow, lastRow), 0, rootIndex()); + + if (firstIdx == lastIdx) + return visualRect(firstIdx); + + QRegion firstRegion = visualRect(firstIdx); + QRegion lastRegion = visualRect(lastIdx); + return firstRegion.unite(lastRegion); +} + +/*! + \reimp +*/ +void QColumnView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command) +{ + Q_UNUSED(rect); + Q_UNUSED(command); +} + +/*! + \reimp +*/ +void QColumnView::setSelectionModel(QItemSelectionModel *newSelectionModel) +{ + Q_D(const QColumnView); + for (int i = 0; i < d->columns.size(); ++i) { + if (d->columns.at(i)->selectionModel() == selectionModel()) { + d->columns.at(i)->setSelectionModel(newSelectionModel); + break; + } + } + QAbstractItemView::setSelectionModel(newSelectionModel); +} + +/*! + \reimp +*/ +QSize QColumnView::sizeHint() const +{ + Q_D(const QColumnView); + QSize sizeHint; + for (int i = 0; i < d->columns.size(); ++i) { + sizeHint += d->columns.at(i)->sizeHint(); + } + return sizeHint.expandedTo(QAbstractItemView::sizeHint()); +} + +/*! + \internal + Move all widgets from the corner grip and to the right + */ +void QColumnViewPrivate::_q_gripMoved(int offset) +{ + Q_Q(QColumnView); + + QObject *grip = q->sender(); + Q_ASSERT(grip); + + if (q->isRightToLeft()) + offset = -1 * offset; + + bool found = false; + for (int i = 0; i < columns.size(); ++i) { + if (!found && columns.at(i)->cornerWidget() == grip) { + found = true; + columnSizes[i] = columns.at(i)->width(); + if (q->isRightToLeft()) + columns.at(i)->move(columns.at(i)->x() + offset, 0); + continue; + } + if (!found) + continue; + + int currentX = columns.at(i)->x(); + columns.at(i)->move(currentX + offset, 0); + } + + updateScrollbars(); +} + +/*! + \internal + + Find where the current columns intersect parent's columns + + Delete any extra columns and insert any needed columns. + */ +void QColumnViewPrivate::closeColumns(const QModelIndex &parent, bool build) +{ + if (columns.isEmpty()) + return; + + bool clearAll = !parent.isValid(); + bool passThroughRoot = false; + + QList<QModelIndex> dirsToAppend; + + // Find the last column that matches the parent's tree + int currentColumn = -1; + QModelIndex parentIndex = parent; + while (currentColumn == -1 && parentIndex.isValid()) { + if (columns.isEmpty()) + break; + parentIndex = parentIndex.parent(); + if (root == parentIndex) + passThroughRoot = true; + if (!parentIndex.isValid()) + break; + for (int i = columns.size() - 1; i >= 0; --i) { + if (columns.at(i)->rootIndex() == parentIndex) { + currentColumn = i; + break; + } + } + if (currentColumn == -1) + dirsToAppend.append(parentIndex); + } + + // Someone wants to go to an index that can be reached without changing + // the root index, don't allow them + if (!clearAll && !passThroughRoot && currentColumn == -1) + return; + + if (currentColumn == -1 && parent.isValid()) + currentColumn = 0; + + // Optimization so we don't go deleting and then creating the same thing + bool alreadyExists = false; + if (build && columns.size() > currentColumn + 1) { + bool viewingParent = (columns.at(currentColumn + 1)->rootIndex() == parent); + bool viewingChild = (!model->hasChildren(parent) + && !columns.at(currentColumn + 1)->rootIndex().isValid()); + if (viewingParent || viewingChild) { + currentColumn++; + alreadyExists = true; + } + } + + // Delete columns that don't match our path + for (int i = columns.size() - 1; i > currentColumn; --i) { + QAbstractItemView* notShownAnymore = columns.at(i); + columns.removeAt(i); + notShownAnymore->setVisible(false); + if (notShownAnymore != previewColumn) + notShownAnymore->deleteLater(); + } + + if (columns.isEmpty()) { + offset = 0; + updateScrollbars(); + } + + // Now fill in missing columns + while (!dirsToAppend.isEmpty()) { + QAbstractItemView *newView = createColumn(dirsToAppend.takeLast(), true); + if (!dirsToAppend.isEmpty()) + newView->setCurrentIndex(dirsToAppend.last()); + } + + if (build && !alreadyExists) + createColumn(parent, false); +} + +void QColumnViewPrivate::_q_clicked(const QModelIndex &index) +{ + Q_Q(QColumnView); + QModelIndex parent = index.parent(); + QAbstractItemView *columnClicked = 0; + for (int column = 0; column < columns.count(); ++column) { + if (columns.at(column)->rootIndex() == parent) { + columnClicked = columns[column]; + break; + } + } + if (q->selectionModel() && columnClicked) { + QItemSelectionModel::SelectionFlags flags = QItemSelectionModel::Current; + if (columnClicked->selectionModel()->isSelected(index)) + flags |= QItemSelectionModel::Select; + q->selectionModel()->setCurrentIndex(index, flags); + } +} + +/*! + \internal + Create a new column for \a index. A grip is attached if requested and it is shown + if requested. + + Return the new view + + \sa createColumn() setPreviewWidget() + \sa doLayout() +*/ +QAbstractItemView *QColumnViewPrivate::createColumn(const QModelIndex &index, bool show) +{ + Q_Q(QColumnView); + QAbstractItemView *view = 0; + if (model->hasChildren(index)) { + view = q->createColumn(index); + q->connect(view, SIGNAL(clicked(const QModelIndex &)), + q, SLOT(_q_clicked(const QModelIndex &))); + } else { + if (!previewColumn) + setPreviewWidget(new QWidget(q)); + view = previewColumn; + view->setMinimumWidth(qMax(view->minimumWidth(), previewWidget->minimumWidth())); + } + + q->connect(view, SIGNAL(activated(const QModelIndex &)), + q, SIGNAL(activated(const QModelIndex &))); + q->connect(view, SIGNAL(clicked(const QModelIndex &)), + q, SIGNAL(clicked(const QModelIndex &))); + q->connect(view, SIGNAL(doubleClicked(const QModelIndex &)), + q, SIGNAL(doubleClicked(const QModelIndex &))); + q->connect(view, SIGNAL(entered(const QModelIndex &)), + q, SIGNAL(entered(const QModelIndex &))); + q->connect(view, SIGNAL(pressed(const QModelIndex &)), + q, SIGNAL(pressed(const QModelIndex &))); + + view->setFocusPolicy(Qt::NoFocus); + view->setParent(q->viewport()); + Q_ASSERT(view); + + // Setup corner grip + if (showResizeGrips) { + QColumnViewGrip *grip = new QColumnViewGrip(view); + view->setCornerWidget(grip); + q->connect(grip, SIGNAL(gripMoved(int)), q, SLOT(_q_gripMoved(int))); + } + + if (columnSizes.count() > columns.count()) { + view->setGeometry(0, 0, columnSizes.at(columns.count()), q->viewport()->height()); + } else { + int initialWidth = view->sizeHint().width(); + if (q->isRightToLeft()) + view->setGeometry(q->viewport()->width() - initialWidth, 0, initialWidth, q->viewport()->height()); + else + view->setGeometry(0, 0, initialWidth, q->viewport()->height()); + columnSizes.resize(qMax(columnSizes.count(), columns.count() + 1)); + columnSizes[columns.count()] = initialWidth; + } + if (!columns.isEmpty() && columns.last()->isHidden()) + columns.last()->setVisible(true); + + columns.append(view); + doLayout(); + updateScrollbars(); + if (show && view->isHidden()) + view->setVisible(true); + return view; +} + +/*! + \fn void QColumnView::updatePreviewWidget(const QModelIndex &index) + + This signal is emitted when the preview widget should be updated to + provide rich information about \a index + + \sa previewWidget() + */ + +/*! + To use a custom widget for the final column when you select + an item overload this function and return a widget. + \a index is the root index that will be assigned to the view. + + Return the new view. QColumnView will automatically take ownership of the widget. + + \sa setPreviewWidget() + */ +QAbstractItemView *QColumnView::createColumn(const QModelIndex &index) +{ + QListView *view = new QListView(viewport()); + + initializeColumn(view); + + view->setRootIndex(index); + if (model()->canFetchMore(index)) + model()->fetchMore(index); + + return view; +} + +/*! + Copies the behavior and options of the column view and applies them to + the \a column such as the iconSize(), textElideMode() and + alternatingRowColors(). This can be useful when reimplementing + createColumn(). + + \since 4.4 + \sa createColumn() + */ +void QColumnView::initializeColumn(QAbstractItemView *column) const +{ + Q_D(const QColumnView); + + column->setFrameShape(QFrame::NoFrame); + column->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + column->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + column->setMinimumWidth(100); + column->setAttribute(Qt::WA_MacShowFocusRect, false); + +#ifndef QT_NO_DRAGANDDROP + column->setDragDropMode(dragDropMode()); + column->setDragDropOverwriteMode(dragDropOverwriteMode()); + column->setDropIndicatorShown(showDropIndicator()); +#endif + column->setAlternatingRowColors(alternatingRowColors()); + column->setAutoScroll(hasAutoScroll()); + column->setEditTriggers(editTriggers()); + column->setHorizontalScrollMode(horizontalScrollMode()); + column->setIconSize(iconSize()); + column->setSelectionBehavior(selectionBehavior()); + column->setSelectionMode(selectionMode()); + column->setTabKeyNavigation(tabKeyNavigation()); + column->setTextElideMode(textElideMode()); + column->setVerticalScrollMode(verticalScrollMode()); + + column->setModel(model()); + + // Copy the custom delegate per row + QMapIterator<int, QPointer<QAbstractItemDelegate> > i(d->rowDelegates); + while (i.hasNext()) { + i.next(); + column->setItemDelegateForRow(i.key(), i.value()); + } + + // set the delegate to be the columnview delegate + QAbstractItemDelegate *delegate = column->itemDelegate(); + column->setItemDelegate(d->itemDelegate); + delete delegate; +} + +/*! + Returns the preview widget, or 0 if there is none. + + \sa setPreviewWidget(), updatePreviewWidget() +*/ +QWidget *QColumnView::previewWidget() const +{ + Q_D(const QColumnView); + return d->previewWidget; +} + +/*! + Sets the preview \a widget. + + The \a widget becomes a child of the column view, and will be + destroyed when the column area is deleted or when a new widget is + set. + + \sa previewWidget(), updatePreviewWidget() +*/ +void QColumnView::setPreviewWidget(QWidget *widget) +{ + Q_D(QColumnView); + d->setPreviewWidget(widget); +} + +/*! + \internal +*/ +void QColumnViewPrivate::setPreviewWidget(QWidget *widget) +{ + Q_Q(QColumnView); + if (previewColumn) { + if (!columns.isEmpty() && columns.last() == previewColumn) + columns.removeLast(); + previewColumn->deleteLater(); + } + QColumnViewPreviewColumn *column = new QColumnViewPreviewColumn(q); + column->setPreviewWidget(widget); + previewColumn = column; + previewColumn->hide(); + previewColumn->setFrameShape(QFrame::NoFrame); + previewColumn->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + previewColumn->setSelectionMode(QAbstractItemView::NoSelection); + previewColumn->setMinimumWidth(qMax(previewColumn->verticalScrollBar()->width(), + previewColumn->minimumWidth())); + previewWidget = widget; + previewWidget->setParent(previewColumn->viewport()); +} + +/*! + Sets the column widths to the values given in the \a list. Extra values in the list are + kept and used when the columns are created. + + If list contains too few values, only width of the rest of the columns will not be modified. + + \sa columnWidths(), createColumn() +*/ +void QColumnView::setColumnWidths(const QList<int> &list) +{ + Q_D(QColumnView); + int i = 0; + for (; (i < list.count() && i < d->columns.count()); ++i) { + d->columns.at(i)->resize(list.at(i), d->columns.at(i)->height()); + d->columnSizes[i] = list.at(i); + } + for (; i < list.count(); ++i) + d->columnSizes.append(list.at(i)); +} + +/*! + Returns a list of the width of all the columns in this view. + + \sa setColumnWidths() +*/ +QList<int> QColumnView::columnWidths() const +{ + Q_D(const QColumnView); + QList<int> list; + for (int i = 0; i < d->columns.count(); ++i) + list.append(d->columnSizes.at(i)); + return list; +} + +/*! + \reimp +*/ +void QColumnView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ + Q_D(QColumnView); + if (!current.isValid()) { + QAbstractItemView::currentChanged(current, previous); + return; + } + + QModelIndex currentParent = current.parent(); + // optimize for just moving up/down in a list where the child view doesn't change + if (currentParent == previous.parent() + && model()->hasChildren(current) && model()->hasChildren(previous)) { + for (int i = 0; i < d->columns.size(); ++i) { + if (currentParent == d->columns.at(i)->rootIndex()) { + if (d->columns.size() > i + 1) { + QAbstractItemView::currentChanged(current, previous); + return; + } + break; + } + } + } + + // Scrolling to the right we need to have an empty spot + bool found = false; + if (currentParent == previous) { + for (int i = 0; i < d->columns.size(); ++i) { + if (currentParent == d->columns.at(i)->rootIndex()) { + found = true; + if (d->columns.size() < i + 2) { + d->createColumn(current, false); + } + break; + } + } + } + if (!found) + d->closeColumns(current, true); + + if (!model()->hasChildren(current)) + emit updatePreviewWidget(current); + + QAbstractItemView::currentChanged(current, previous); +} + +/* + We have change the current column and need to update focus and selection models + on the new current column. +*/ +void QColumnViewPrivate::_q_changeCurrentColumn() +{ + Q_Q(QColumnView); + if (columns.isEmpty()) + return; + + QModelIndex current = q->currentIndex(); + if (!current.isValid()) + return; + + // We might have scrolled far to the left so we need to close all of the children + closeColumns(current, true); + + // Set up the "current" column with focus + int currentColumn = qMax(0, columns.size() - 2); + QAbstractItemView *parentColumn = columns.at(currentColumn); + if (q->hasFocus()) + parentColumn->setFocus(Qt::OtherFocusReason); + q->setFocusProxy(parentColumn); + + // find the column that is our current selection model and give it a new one. + for (int i = 0; i < columns.size(); ++i) { + if (columns.at(i)->selectionModel() == q->selectionModel()) { + QItemSelectionModel *replacementSelectionModel = + new QItemSelectionModel(parentColumn->model()); + replacementSelectionModel->setCurrentIndex( + q->selectionModel()->currentIndex(), QItemSelectionModel::Current); + replacementSelectionModel->select( + q->selectionModel()->selection(), QItemSelectionModel::Select); + QAbstractItemView *view = columns.at(i); + view->setSelectionModel(replacementSelectionModel); + view->setFocusPolicy(Qt::NoFocus); + if (columns.size() > i + 1) + view->setCurrentIndex(columns.at(i+1)->rootIndex()); + break; + } + } + parentColumn->selectionModel()->deleteLater(); + parentColumn->setFocusPolicy(Qt::StrongFocus); + parentColumn->setSelectionModel(q->selectionModel()); + // We want the parent selection to stay highlighted (but dimmed depending upon the color theme) + if (currentColumn > 0) { + parentColumn = columns.at(currentColumn - 1); + if (parentColumn->currentIndex() != current.parent()) + parentColumn->setCurrentIndex(current.parent()); + } + + if (columns.last()->isHidden()) { + columns.last()->setVisible(true); + } + if (columns.last()->selectionModel()) + columns.last()->selectionModel()->clear(); + updateScrollbars(); +} + +/*! + \reimp +*/ +void QColumnView::selectAll() +{ + if (!model() || !selectionModel()) + return; + + QModelIndexList indexList = selectionModel()->selectedIndexes(); + QModelIndex parent = rootIndex(); + QItemSelection selection; + if (indexList.count() >= 1) + parent = indexList.at(0).parent(); + if (indexList.count() == 1) { + parent = indexList.at(0); + if (!model()->hasChildren(parent)) + parent = parent.parent(); + else + selection.append(QItemSelectionRange(parent, parent)); + } + + QModelIndex tl = model()->index(0, 0, parent); + QModelIndex br = model()->index(model()->rowCount(parent) - 1, + model()->columnCount(parent) - 1, + parent); + selection.append(QItemSelectionRange(tl, br)); + selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); +} + +/* + * private object implementation + */ +QColumnViewPrivate::QColumnViewPrivate() +: QAbstractItemViewPrivate() +,showResizeGrips(true) +,offset(0) +,currentAnimation(ANIMATION_DURATION_MSEC) +,previewWidget(0) +,previewColumn(0) +{ +} + +QColumnViewPrivate::~QColumnViewPrivate() +{ +} + +/*! + \internal + Place all of the columns where they belong inside of the viewport, resize as necessary. +*/ +void QColumnViewPrivate::doLayout() +{ + Q_Q(QColumnView); + if (!model || columns.isEmpty()) + return; + + int viewportHeight = q->viewport()->height(); + int x = columns.at(0)->x(); + + if (q->isRightToLeft()) { + x = q->viewport()->width() + q->horizontalOffset(); + for (int i = 0; i < columns.size(); ++i) { + QAbstractItemView *view = columns.at(i); + x -= view->width(); + if (x != view->x() || viewportHeight != view->height()) + view->setGeometry(x, 0, view->width(), viewportHeight); + } + } else { + for (int i = 0; i < columns.size(); ++i) { + QAbstractItemView *view = columns.at(i); + int currentColumnWidth = view->width(); + if (x != view->x() || viewportHeight != view->height()) + view->setGeometry(x, 0, currentColumnWidth, viewportHeight); + x += currentColumnWidth; + } + } +} + +/*! + \internal + + Draws a delegate with a > if an object has children. + + \sa {Model/View Programming}, QItemDelegate +*/ +void QColumnViewDelegate::paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + drawBackground(painter, option, index ); + + bool reverse = (option.direction == Qt::RightToLeft); + int width = ((option.rect.height() * 2) / 3); + // Modify the options to give us room to add an arrow + QStyleOptionViewItemV4 opt = option; + if (reverse) + opt.rect.adjust(width,0,0,0); + else + opt.rect.adjust(0,0,-width,0); + + if (!(index.model()->flags(index) & Qt::ItemIsEnabled)) { + opt.showDecorationSelected = true; + opt.state |= QStyle::State_Selected; + } + + QItemDelegate::paint(painter, opt, index); + + if (reverse) + opt.rect = QRect(option.rect.x(), option.rect.y(), width, option.rect.height()); + else + opt.rect = QRect(option.rect.x() + option.rect.width() - width, option.rect.y(), + width, option.rect.height()); + + // Draw > + if (index.model()->hasChildren(index)) { + const QWidget *view = opt.widget; + QStyle *style = view ? view->style() : qApp->style(); + style->drawPrimitive(QStyle::PE_IndicatorColumnViewArrow, &opt, painter, view); + } +} + +QT_END_NAMESPACE + +#include "moc_qcolumnview.cpp" + +#endif // QT_NO_COLUMNVIEW diff --git a/src/gui/itemviews/qcolumnview.h b/src/gui/itemviews/qcolumnview.h new file mode 100644 index 0000000..ca9134c --- /dev/null +++ b/src/gui/itemviews/qcolumnview.h @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOLUMNVIEW_H +#define QCOLUMNVIEW_H + +#include <QtGui/qabstractitemview.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_COLUMNVIEW + +class QColumnViewPrivate; + +class Q_GUI_EXPORT QColumnView : public QAbstractItemView { + +Q_OBJECT + Q_PROPERTY(bool resizeGripsVisible READ resizeGripsVisible WRITE setResizeGripsVisible) + +Q_SIGNALS: + void updatePreviewWidget(const QModelIndex &index); + +public: + explicit QColumnView(QWidget *parent = 0); + ~QColumnView(); + + // QAbstractItemView overloads + QModelIndex indexAt(const QPoint &point) const; + void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible); + QSize sizeHint() const; + QRect visualRect(const QModelIndex &index) const; + void setModel(QAbstractItemModel *model); + void setSelectionModel(QItemSelectionModel * selectionModel); + void setRootIndex(const QModelIndex &index); + void selectAll(); + + // QColumnView functions + void setResizeGripsVisible(bool visible); + bool resizeGripsVisible() const; + + QWidget *previewWidget() const; + void setPreviewWidget(QWidget *widget); + + void setColumnWidths(const QList<int> &list); + QList<int> columnWidths() const; + +protected: + QColumnView(QColumnViewPrivate &dd, QWidget *parent = 0); + + // QAbstractItemView overloads + bool isIndexHidden(const QModelIndex &index) const; + QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers); + void resizeEvent(QResizeEvent *event); + void setSelection(const QRect & rect, QItemSelectionModel::SelectionFlags command); + QRegion visualRegionForSelection(const QItemSelection &selection) const; + int horizontalOffset() const; + int verticalOffset() const; + void scrollContentsBy(int dx, int dy); + + // QColumnView functions + virtual QAbstractItemView* createColumn(const QModelIndex &rootIndex); + void initializeColumn(QAbstractItemView *column) const; + +protected Q_SLOTS: + // QAbstractItemView overloads + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); + +private: + Q_DECLARE_PRIVATE(QColumnView) + Q_DISABLE_COPY(QColumnView) + Q_PRIVATE_SLOT(d_func(), void _q_gripMoved(int)) + Q_PRIVATE_SLOT(d_func(), void _q_changeCurrentColumn()) + Q_PRIVATE_SLOT(d_func(), void _q_clicked(const QModelIndex &)) +}; + +#endif // QT_NO_COLUMNVIEW + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QCOLUMNVIEW_H + diff --git a/src/gui/itemviews/qcolumnview_p.h b/src/gui/itemviews/qcolumnview_p.h new file mode 100644 index 0000000..8f75bec --- /dev/null +++ b/src/gui/itemviews/qcolumnview_p.h @@ -0,0 +1,184 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOLUMNVIEW_P_H +#define QCOLUMNVIEW_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qcolumnview.h" + +#ifndef QT_NO_QCOlUMNVIEW + +#include <private/qabstractitemview_p.h> + +#include <QtCore/qabstractitemmodel.h> +#include <QtCore/qtimeline.h> +#include <QtGui/qabstractitemdelegate.h> +#include <QtGui/qabstractitemview.h> +#include <QtGui/qitemdelegate.h> +#include <qlistview.h> +#include <qevent.h> +#include <qscrollbar.h> + +QT_BEGIN_NAMESPACE + +class QColumnViewPreviewColumn : public QAbstractItemView { + +public: + QColumnViewPreviewColumn(QWidget *parent) : QAbstractItemView(parent), previewWidget(0) { + } + + void setPreviewWidget(QWidget *widget) { + previewWidget = widget; + setMinimumWidth(previewWidget->minimumWidth()); + } + + void resizeEvent(QResizeEvent * event){ + if (!previewWidget) + return; + previewWidget->resize( + qMax(previewWidget->minimumWidth(), event->size().width()), + previewWidget->height()); + QSize p = viewport()->size(); + QSize v = previewWidget->size(); + horizontalScrollBar()->setRange(0, v.width() - p.width()); + horizontalScrollBar()->setPageStep(p.width()); + verticalScrollBar()->setRange(0, v.height() - p.height()); + verticalScrollBar()->setPageStep(p.height()); + + QAbstractScrollArea::resizeEvent(event); + } + + QRect visualRect(const QModelIndex &) const + { + return QRect(); + } + void scrollTo(const QModelIndex &, ScrollHint) + { + } + QModelIndex indexAt(const QPoint &) const + { + return QModelIndex(); + } + QModelIndex moveCursor(CursorAction, Qt::KeyboardModifiers) + { + return QModelIndex(); + } + int horizontalOffset () const { + return 0; + } + int verticalOffset () const { + return 0; + } + QRegion visualRegionForSelection(const QItemSelection &) const + { + return QRegion(); + } + bool isIndexHidden(const QModelIndex &) const + { + return false; + } + void setSelection(const QRect &, QItemSelectionModel::SelectionFlags) + { + } +private: + QWidget *previewWidget; +}; + +class Q_AUTOTEST_EXPORT QColumnViewPrivate : public QAbstractItemViewPrivate +{ + Q_DECLARE_PUBLIC(QColumnView) + +public: + QColumnViewPrivate(); + ~QColumnViewPrivate(); + void initialize(); + + QAbstractItemView *createColumn(const QModelIndex &index, bool show); + + void updateScrollbars(); + void closeColumns(const QModelIndex &parent = QModelIndex(), bool build = false); + void doLayout(); + void setPreviewWidget(QWidget *widget); + + void _q_gripMoved(int offset); + void _q_changeCurrentColumn(); + void _q_clicked(const QModelIndex &index); + + QList<QAbstractItemView*> columns; + QVector<int> columnSizes; // used during init and corner moving + bool showResizeGrips; + int offset; + QTimeLine currentAnimation; + QWidget *previewWidget; + QAbstractItemView *previewColumn; +}; + +/*! + * This is a delegate that will paint the triangle + */ +class QColumnViewDelegate : public QItemDelegate +{ + +public: + explicit QColumnViewDelegate(QObject *parent = 0) : QItemDelegate(parent) {}; + ~QColumnViewDelegate() {}; + + void paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const; +}; +#endif // QT_NO_QCOLUMNVIEW + + +QT_END_NAMESPACE +#endif //QCOLUMNVIEW_P_H + diff --git a/src/gui/itemviews/qcolumnviewgrip.cpp b/src/gui/itemviews/qcolumnviewgrip.cpp new file mode 100644 index 0000000..5da70f6 --- /dev/null +++ b/src/gui/itemviews/qcolumnviewgrip.cpp @@ -0,0 +1,194 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT_NO_QCOLUMNVIEW + +#include "qcolumnviewgrip_p.h" +#include <qstyleoption.h> +#include <qpainter.h> +#include <qbrush.h> +#include <qevent.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +/* + \internal + class QColumnViewGrip + + QColumnViewGrip is created to go inside QAbstractScrollArea's corner. + When the mouse it moved it will resize the scroll area and emit's a signal. + */ + +/* + \internal + \fn void QColumnViewGrip::gripMoved() + Signal that is emitted when the grip moves the parent widget. + */ + +/*! + Creates a new QColumnViewGrip with the given \a parent to view a model. + Use setModel() to set the model. +*/ +QColumnViewGrip::QColumnViewGrip(QWidget *parent) +: QWidget(*new QColumnViewGripPrivate, parent, 0) +{ +#ifndef QT_NO_CURSOR + setCursor(Qt::SplitHCursor); +#endif +} + +/*! + \internal +*/ +QColumnViewGrip::QColumnViewGrip(QColumnViewGripPrivate & dd, QWidget *parent, Qt::WFlags f) +: QWidget(dd, parent, f) +{ +} + +/*! + Destroys the view. +*/ +QColumnViewGrip::~QColumnViewGrip() +{ +} + +/*! + Attempt to resize the parent object by \a offset + returns the amount of offset that it was actually able to resized +*/ +int QColumnViewGrip::moveGrip(int offset) +{ + QWidget *parentWidget = (QWidget*)parent(); + + // first resize the parent + int oldWidth = parentWidget->width(); + int newWidth = oldWidth; + if (isRightToLeft()) + newWidth -= offset; + else + newWidth += offset; + newWidth = qMax(parentWidget->minimumWidth(), newWidth); + parentWidget->resize(newWidth, parentWidget->height()); + + // Then have the view move the widget + int realOffset = parentWidget->width() - oldWidth; + int oldX = parentWidget->x(); + if (realOffset != 0) + emit gripMoved(realOffset); + if (isRightToLeft()) + realOffset = -1 * (oldX - parentWidget->x()); + return realOffset; +} + +/*! + \reimp +*/ +void QColumnViewGrip::paintEvent(QPaintEvent *event) +{ + QPainter painter(this); + QStyleOption opt; + opt.initFrom(this); + style()->drawControl(QStyle::CE_ColumnViewGrip, &opt, &painter, this); + event->accept(); +} + +/*! + \reimp + Resize the parent window to the sizeHint +*/ +void QColumnViewGrip::mouseDoubleClickEvent(QMouseEvent *event) +{ + Q_UNUSED(event); + QWidget *parentWidget = (QWidget*)parent(); + int offset = parentWidget->sizeHint().width() - parentWidget->width(); + if (isRightToLeft()) + offset *= -1; + moveGrip(offset); + event->accept(); +} + +/*! + \reimp + Begin watching for mouse movements +*/ +void QColumnViewGrip::mousePressEvent(QMouseEvent *event) +{ + Q_D(QColumnViewGrip); + d->originalXLocation = event->globalX(); + event->accept(); +} + +/*! + \reimp + Calculate the movement of the grip and moveGrip() and emit gripMoved +*/ +void QColumnViewGrip::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QColumnViewGrip); + int offset = event->globalX() - d->originalXLocation; + d->originalXLocation = moveGrip(offset) + d->originalXLocation; + event->accept(); +} + +/*! + \reimp + Stop watching for mouse movements +*/ +void QColumnViewGrip::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(QColumnViewGrip); + d->originalXLocation = -1; + event->accept(); +} + +/* + * private object implementation + */ +QColumnViewGripPrivate::QColumnViewGripPrivate() +: QWidgetPrivate(), +originalXLocation(-1) +{ +} + +QT_END_NAMESPACE + +#endif // QT_NO_QCOLUMNVIEW diff --git a/src/gui/itemviews/qcolumnviewgrip_p.h b/src/gui/itemviews/qcolumnviewgrip_p.h new file mode 100644 index 0000000..12515b0 --- /dev/null +++ b/src/gui/itemviews/qcolumnviewgrip_p.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOLUMNVIEWGRIP_P_H +#define QCOLUMNVIEWGRIP_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qwidget_p.h> + +#ifndef QT_NO_QCOLUMNVIEW + +QT_BEGIN_NAMESPACE + +class QColumnViewGripPrivate; + +class Q_AUTOTEST_EXPORT QColumnViewGrip : public QWidget { + +Q_OBJECT + +Q_SIGNALS: + void gripMoved(int offset); + +public: + explicit QColumnViewGrip(QWidget *parent = 0); + ~QColumnViewGrip(); + int moveGrip(int offset); + +protected: + QColumnViewGrip(QColumnViewGripPrivate &, QWidget *parent = 0, Qt::WFlags f = 0); + void paintEvent(QPaintEvent *event); + void mouseDoubleClickEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void mousePressEvent(QMouseEvent *event); + +private: + Q_DECLARE_PRIVATE(QColumnViewGrip) + Q_DISABLE_COPY(QColumnViewGrip) +}; + +class QColumnViewGripPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QColumnViewGrip) + +public: + QColumnViewGripPrivate(); + ~QColumnViewGripPrivate() {} + + int originalXLocation; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_QCOLUMNVIEW + +#endif //QCOLUMNVIEWGRIP_P_H diff --git a/src/gui/itemviews/qdatawidgetmapper.cpp b/src/gui/itemviews/qdatawidgetmapper.cpp new file mode 100644 index 0000000..909fba1 --- /dev/null +++ b/src/gui/itemviews/qdatawidgetmapper.cpp @@ -0,0 +1,849 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdatawidgetmapper.h" + +#ifndef QT_NO_DATAWIDGETMAPPER + +#include "qabstractitemmodel.h" +#include "qitemdelegate.h" +#include "qmetaobject.h" +#include "qwidget.h" +#include "private/qobject_p.h" +#include "private/qabstractitemmodel_p.h" + +QT_BEGIN_NAMESPACE + +class QDataWidgetMapperPrivate: public QObjectPrivate +{ +public: + Q_DECLARE_PUBLIC(QDataWidgetMapper) + + QDataWidgetMapperPrivate() + : model(QAbstractItemModelPrivate::staticEmptyModel()), delegate(0), + orientation(Qt::Horizontal), submitPolicy(QDataWidgetMapper::AutoSubmit) + { + } + + QAbstractItemModel *model; + QAbstractItemDelegate *delegate; + Qt::Orientation orientation; + QDataWidgetMapper::SubmitPolicy submitPolicy; + QPersistentModelIndex rootIndex; + QPersistentModelIndex currentTopLeft; + + inline int itemCount() + { + return orientation == Qt::Horizontal + ? model->rowCount(rootIndex) + : model->columnCount(rootIndex); + } + + inline int currentIdx() const + { + return orientation == Qt::Horizontal ? currentTopLeft.row() : currentTopLeft.column(); + } + + inline QModelIndex indexAt(int itemPos) + { + return orientation == Qt::Horizontal + ? model->index(currentIdx(), itemPos, rootIndex) + : model->index(itemPos, currentIdx(), rootIndex); + } + + inline void flipEventFilters(QAbstractItemDelegate *oldDelegate, + QAbstractItemDelegate *newDelegate) + { + for (int i = 0; i < widgetMap.count(); ++i) { + QWidget *w = widgetMap.at(i).widget; + if (!w) + continue; + w->removeEventFilter(oldDelegate); + w->installEventFilter(newDelegate); + } + } + + void populate(); + + // private slots + void _q_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void _q_commitData(QWidget *); + void _q_closeEditor(QWidget *, QAbstractItemDelegate::EndEditHint); + void _q_modelDestroyed(); + + struct WidgetMapper + { + inline WidgetMapper(QWidget *w = 0, int c = 0, const QModelIndex &i = QModelIndex()) + : widget(w), section(c), currentIndex(i) {} + inline WidgetMapper(QWidget *w, int c, const QModelIndex &i, const QByteArray &p) + : widget(w), section(c), currentIndex(i), property(p) {} + + QPointer<QWidget> widget; + int section; + QPersistentModelIndex currentIndex; + QByteArray property; + }; + + void populate(WidgetMapper &m); + int findWidget(QWidget *w) const; + + bool commit(const WidgetMapper &m); + + QList<WidgetMapper> widgetMap; +}; + +int QDataWidgetMapperPrivate::findWidget(QWidget *w) const +{ + for (int i = 0; i < widgetMap.count(); ++i) { + if (widgetMap.at(i).widget == w) + return i; + } + return -1; +} + +bool QDataWidgetMapperPrivate::commit(const WidgetMapper &m) +{ + if (m.widget.isNull()) + return true; // just ignore + + if (!m.currentIndex.isValid()) + return false; + + // Create copy to avoid passing the widget mappers data + QModelIndex idx = m.currentIndex; + if (m.property.isEmpty()) + delegate->setModelData(m.widget, model, idx); + else + model->setData(idx, m.widget->property(m.property), Qt::EditRole); + + return true; +} + +void QDataWidgetMapperPrivate::populate(WidgetMapper &m) +{ + if (m.widget.isNull()) + return; + + m.currentIndex = indexAt(m.section); + if (m.property.isEmpty()) + delegate->setEditorData(m.widget, m.currentIndex); + else + m.widget->setProperty(m.property, m.currentIndex.data(Qt::EditRole)); +} + +void QDataWidgetMapperPrivate::populate() +{ + for (int i = 0; i < widgetMap.count(); ++i) + populate(widgetMap[i]); +} + +static bool qContainsIndex(const QModelIndex &idx, const QModelIndex &topLeft, + const QModelIndex &bottomRight) +{ + return idx.row() >= topLeft.row() && idx.row() <= bottomRight.row() + && idx.column() >= topLeft.column() && idx.column() <= bottomRight.column(); +} + +void QDataWidgetMapperPrivate::_q_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + if (topLeft.parent() != rootIndex) + return; // not in our hierarchy + + for (int i = 0; i < widgetMap.count(); ++i) { + WidgetMapper &m = widgetMap[i]; + if (qContainsIndex(m.currentIndex, topLeft, bottomRight)) + populate(m); + } +} + +void QDataWidgetMapperPrivate::_q_commitData(QWidget *w) +{ + if (submitPolicy == QDataWidgetMapper::ManualSubmit) + return; + + int idx = findWidget(w); + if (idx == -1) + return; // not our widget + + commit(widgetMap.at(idx)); +} + +class QFocusHelper: public QWidget +{ +public: + bool focusNextPrevChild(bool next) + { + return QWidget::focusNextPrevChild(next); + } + + static inline void focusNextPrevChild(QWidget *w, bool next) + { + static_cast<QFocusHelper *>(w)->focusNextPrevChild(next); + } +}; + +void QDataWidgetMapperPrivate::_q_closeEditor(QWidget *w, QAbstractItemDelegate::EndEditHint hint) +{ + int idx = findWidget(w); + if (idx == -1) + return; // not our widget + + switch (hint) { + case QAbstractItemDelegate::RevertModelCache: { + populate(widgetMap[idx]); + break; } + case QAbstractItemDelegate::EditNextItem: + QFocusHelper::focusNextPrevChild(w, true); + break; + case QAbstractItemDelegate::EditPreviousItem: + QFocusHelper::focusNextPrevChild(w, false); + break; + case QAbstractItemDelegate::SubmitModelCache: + case QAbstractItemDelegate::NoHint: + // nothing + break; + } +} + +void QDataWidgetMapperPrivate::_q_modelDestroyed() +{ + Q_Q(QDataWidgetMapper); + + model = 0; + q->setModel(QAbstractItemModelPrivate::staticEmptyModel()); +} + +/*! + \class QDataWidgetMapper + \brief The QDataWidgetMapper class provides mapping between a section + of a data model to widgets. + \since 4.2 + \ingroup model-view + \ingroup advanced + + QDataWidgetMapper can be used to create data-aware widgets by mapping + them to sections of an item model. A section is a column of a model + if the orientation is horizontal (the default), otherwise a row. + + Every time the current index changes, each widget is updated with data + from the model via the property specified when its mapping was made. + If the user edits the contents of a widget, the changes are read using + the same property and written back to the model. + By default, each widget's \l{Q_PROPERTY()}{user property} is used to + transfer data between the model and the widget. Since Qt 4.3, an + additional addMapping() function enables a named property to be used + instead of the default user property. + + It is possible to set an item delegate to support custom widgets. By default, + a QItemDelegate is used to synchronize the model with the widgets. + + Let us assume that we have an item model named \c{model} with the following contents: + + \table + \row \o 1 \o Nokia Corporation and/or its subsidiary(-ies) \o Oslo + \row \o 2 \o Trolltech Pty \o Brisbane + \row \o 3 \o Trolltech Inc \o Palo Alto + \row \o 4 \o Trolltech China \o Beijing + \row \o 5 \o Trolltech GmbH \o Berlin + \endtable + + The following code will map the columns of the model to widgets called \c mySpinBox, + \c myLineEdit and \c{myCountryChooser}: + + \snippet doc/src/snippets/code/src_gui_itemviews_qdatawidgetmapper.cpp 0 + + After the call to toFirst(), \c mySpinBox displays the value \c{1}, \c myLineEdit + displays \c {Nokia Corporation and/or its subsidiary(-ies)} and \c myCountryChooser displays \c{Oslo}. The + navigational functions toFirst(), toNext(), toPrevious(), toLast() and setCurrentIndex() + can be used to navigate in the model and update the widgets with contents from + the model. + + The setRootIndex() function enables a particular item in a model to be + specified as the root index - children of this item will be mapped to + the relevant widgets in the user interface. + + QDataWidgetMapper supports two submit policies, \c AutoSubmit and \c{ManualSubmit}. + \c AutoSubmit will update the model as soon as the current widget loses focus, + \c ManualSubmit will not update the model unless submit() is called. \c ManualSubmit + is useful when displaying a dialog that lets the user cancel all modifications. + Also, other views that display the model won't update until the user finishes + all their modifications and submits. + + Note that QDataWidgetMapper keeps track of external modifications. If the contents + of the model are updated in another module of the application, the widgets are + updated as well. + + \sa QAbstractItemModel, QAbstractItemDelegate + */ + +/*! \enum QDataWidgetMapper::SubmitPolicy + + This enum describes the possible submit policies a QDataWidgetMapper + supports. + + \value AutoSubmit Whenever a widget loses focus, the widget's current + value is set to the item model. + \value ManualSubmit The model is not updated until submit() is called. + */ + +/*! + \fn void QDataWidgetMapper::currentIndexChanged(int index) + + This signal is emitted after the current index has changed and + all widgets were populated with new data. \a index is the new + current index. + + \sa currentIndex(), setCurrentIndex() + */ + +/*! + Constructs a new QDataWidgetMapper with parent object \a parent. + By default, the orientation is horizontal and the submit policy + is \c{AutoSubmit}. + + \sa setOrientation(), setSubmitPolicy() + */ +QDataWidgetMapper::QDataWidgetMapper(QObject *parent) + : QObject(*new QDataWidgetMapperPrivate, parent) +{ + setItemDelegate(new QItemDelegate(this)); +} + +/*! + Destroys the object. + */ +QDataWidgetMapper::~QDataWidgetMapper() +{ +} + +/*! + Sets the current model to \a model. If another model was set, + all mappings to that old model are cleared. + + \sa model() + */ +void QDataWidgetMapper::setModel(QAbstractItemModel *model) +{ + Q_D(QDataWidgetMapper); + + if (d->model == model) + return; + + if (d->model) { + disconnect(d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, + SLOT(_q_dataChanged(QModelIndex,QModelIndex))); + disconnect(d->model, SIGNAL(destroyed()), this, + SLOT(_q_modelDestroyed())); + } + clearMapping(); + d->rootIndex = QModelIndex(); + d->currentTopLeft = QModelIndex(); + + d->model = model; + + connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + SLOT(_q_dataChanged(QModelIndex,QModelIndex))); + connect(model, SIGNAL(destroyed()), SLOT(_q_modelDestroyed())); +} + +/*! + Returns the current model. + + \sa setModel() + */ +QAbstractItemModel *QDataWidgetMapper::model() const +{ + Q_D(const QDataWidgetMapper); + return d->model == QAbstractItemModelPrivate::staticEmptyModel() + ? static_cast<QAbstractItemModel *>(0) + : d->model; +} + +/*! + Sets the item delegate to \a delegate. The delegate will be used to write + data from the model into the widget and from the widget to the model, + using QAbstractItemDelegate::setEditorData() and QAbstractItemDelegate::setModelData(). + + The delegate also decides when to apply data and when to change the editor, + using QAbstractItemDelegate::commitData() and QAbstractItemDelegate::closeEditor(). + + \warning You should not share the same instance of a delegate between widget mappers + or views. Doing so can cause incorrect or unintuitive editing behavior since each + view connected to a given delegate may receive the \l{QAbstractItemDelegate::}{closeEditor()} + signal, and attempt to access, modify or close an editor that has already been closed. + */ +void QDataWidgetMapper::setItemDelegate(QAbstractItemDelegate *delegate) +{ + Q_D(QDataWidgetMapper); + QAbstractItemDelegate *oldDelegate = d->delegate; + if (oldDelegate) { + disconnect(oldDelegate, SIGNAL(commitData(QWidget*)), this, SLOT(_q_commitData(QWidget*))); + disconnect(oldDelegate, SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)), + this, SLOT(_q_closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint))); + } + + d->delegate = delegate; + + if (delegate) { + connect(delegate, SIGNAL(commitData(QWidget*)), SLOT(_q_commitData(QWidget*))); + connect(delegate, SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)), + SLOT(_q_closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint))); + } + + d->flipEventFilters(oldDelegate, delegate); +} + +/*! + Returns the current item delegate. + */ +QAbstractItemDelegate *QDataWidgetMapper::itemDelegate() const +{ + Q_D(const QDataWidgetMapper); + return d->delegate; +} + +/*! + Sets the root item to \a index. This can be used to display + a branch of a tree. Pass an invalid model index to display + the top-most branch. + + \sa rootIndex() + */ +void QDataWidgetMapper::setRootIndex(const QModelIndex &index) +{ + Q_D(QDataWidgetMapper); + d->rootIndex = index; +} + +/*! + Returns the current root index. + + \sa setRootIndex() +*/ +QModelIndex QDataWidgetMapper::rootIndex() const +{ + Q_D(const QDataWidgetMapper); + return QModelIndex(d->rootIndex); +} + +/*! + Adds a mapping between a \a widget and a \a section from the model. + The \a section is a column in the model if the orientation is + horizontal (the default), otherwise a row. + + For the following example, we assume a model \c myModel that + has two columns: the first one contains the names of people in a + group, and the second column contains their ages. The first column + is mapped to the QLineEdit \c nameLineEdit, and the second is + mapped to the QSpinBox \c{ageSpinBox}: + + \snippet doc/src/snippets/code/src_gui_itemviews_qdatawidgetmapper.cpp 1 + + \bold{Notes:} + \list + \o If the \a widget is already mapped to a section, the + old mapping will be replaced by the new one. + \o Only one-to-one mappings between sections and widgets are allowed. + It is not possible to map a single section to multiple widgets, or to + map a single widget to multiple sections. + \endlist + + \sa removeMapping(), mappedSection(), clearMapping() + */ +void QDataWidgetMapper::addMapping(QWidget *widget, int section) +{ + Q_D(QDataWidgetMapper); + + removeMapping(widget); + d->widgetMap.append(QDataWidgetMapperPrivate::WidgetMapper(widget, section, d->indexAt(section))); + widget->installEventFilter(d->delegate); +} + +/*! + \since 4.3 + + Essentially the same as addMapping(), but adds the possibility to specify + the property to use specifying \a propertyName. + + \sa addMapping() +*/ + +void QDataWidgetMapper::addMapping(QWidget *widget, int section, const QByteArray &propertyName) +{ + Q_D(QDataWidgetMapper); + + removeMapping(widget); + d->widgetMap.append(QDataWidgetMapperPrivate::WidgetMapper(widget, section, d->indexAt(section), propertyName)); + widget->installEventFilter(d->delegate); +} + +/*! + Removes the mapping for the given \a widget. + + \sa addMapping(), clearMapping() + */ +void QDataWidgetMapper::removeMapping(QWidget *widget) +{ + Q_D(QDataWidgetMapper); + + int idx = d->findWidget(widget); + if (idx == -1) + return; + + d->widgetMap.removeAt(idx); + widget->removeEventFilter(d->delegate); +} + +/*! + Returns the section the \a widget is mapped to or -1 + if the widget is not mapped. + + \sa addMapping(), removeMapping() + */ +int QDataWidgetMapper::mappedSection(QWidget *widget) const +{ + Q_D(const QDataWidgetMapper); + + int idx = d->findWidget(widget); + if (idx == -1) + return -1; + + return d->widgetMap.at(idx).section; +} + +/*! + \since 4.3 + Returns the name of the property that is used when mapping + data to the given \a widget. + + \sa mappedSection(), addMapping(), removeMapping() +*/ + +QByteArray QDataWidgetMapper::mappedPropertyName(QWidget *widget) const +{ + Q_D(const QDataWidgetMapper); + + int idx = d->findWidget(widget); + if (idx == -1) + return QByteArray(); + const QDataWidgetMapperPrivate::WidgetMapper &m = d->widgetMap.at(idx); + if (m.property.isEmpty()) + return m.widget->metaObject()->userProperty().name(); + else + return m.property; +} + +/*! + Returns the widget that is mapped at \a section, or + 0 if no widget is mapped at that section. + + \sa addMapping(), removeMapping() + */ +QWidget *QDataWidgetMapper::mappedWidgetAt(int section) const +{ + Q_D(const QDataWidgetMapper); + + for (int i = 0; i < d->widgetMap.count(); ++i) { + if (d->widgetMap.at(i).section == section) + return d->widgetMap.at(i).widget; + } + + return 0; +} + +/*! + Repopulates all widgets with the current data of the model. + All unsubmitted changes will be lost. + + \sa submit(), setSubmitPolicy() + */ +void QDataWidgetMapper::revert() +{ + Q_D(QDataWidgetMapper); + + d->populate(); +} + +/*! + Submits all changes from the mapped widgets to the model. + + For every mapped section, the item delegate reads the current + value from the widget and sets it in the model. Finally, the + model's \l {QAbstractItemModel::}{submit()} method is invoked. + + Returns true if all the values were submitted, otherwise false. + + Note: For database models, QSqlQueryModel::lastError() can be + used to retrieve the last error. + + \sa revert(), setSubmitPolicy() + */ +bool QDataWidgetMapper::submit() +{ + Q_D(QDataWidgetMapper); + + for (int i = 0; i < d->widgetMap.count(); ++i) { + const QDataWidgetMapperPrivate::WidgetMapper &m = d->widgetMap.at(i); + if (!d->commit(m)) + return false; + } + + return d->model->submit(); +} + +/*! + Populates the widgets with data from the first row of the model + if the orientation is horizontal (the default), otherwise + with data from the first column. + + This is equivalent to calling \c setCurrentIndex(0). + + \sa toLast(), setCurrentIndex() + */ +void QDataWidgetMapper::toFirst() +{ + setCurrentIndex(0); +} + +/*! + Populates the widgets with data from the last row of the model + if the orientation is horizontal (the default), otherwise + with data from the last column. + + Calls setCurrentIndex() internally. + + \sa toFirst(), setCurrentIndex() + */ +void QDataWidgetMapper::toLast() +{ + Q_D(QDataWidgetMapper); + setCurrentIndex(d->itemCount() - 1); +} + + +/*! + Populates the widgets with data from the next row of the model + if the orientation is horizontal (the default), otherwise + with data from the next column. + + Calls setCurrentIndex() internally. Does nothing if there is + no next row in the model. + + \sa toPrevious(), setCurrentIndex() + */ +void QDataWidgetMapper::toNext() +{ + Q_D(QDataWidgetMapper); + setCurrentIndex(d->currentIdx() + 1); +} + +/*! + Populates the widgets with data from the previous row of the model + if the orientation is horizontal (the default), otherwise + with data from the previous column. + + Calls setCurrentIndex() internally. Does nothing if there is + no previous row in the model. + + \sa toNext(), setCurrentIndex() + */ +void QDataWidgetMapper::toPrevious() +{ + Q_D(QDataWidgetMapper); + setCurrentIndex(d->currentIdx() - 1); +} + +/*! + \property QDataWidgetMapper::currentIndex + \brief the current row or column + + The widgets are populated with with data from the row at \a index + if the orientation is horizontal (the default), otherwise with + data from the column at \a index. + + \sa setCurrentModelIndex(), toFirst(), toNext(), toPrevious(), toLast() +*/ +void QDataWidgetMapper::setCurrentIndex(int index) +{ + Q_D(QDataWidgetMapper); + + if (index < 0 || index >= d->itemCount()) + return; + d->currentTopLeft = d->orientation == Qt::Horizontal + ? d->model->index(index, 0, d->rootIndex) + : d->model->index(0, index, d->rootIndex); + d->populate(); + + emit currentIndexChanged(index); +} + +int QDataWidgetMapper::currentIndex() const +{ + Q_D(const QDataWidgetMapper); + return d->currentIdx(); +} + +/*! + Sets the current index to the row of the \a index if the + orientation is horizontal (the default), otherwise to the + column of the \a index. + + Calls setCurrentIndex() internally. This convenience slot can be + connected to the signal \l + {QItemSelectionModel::}{currentRowChanged()} or \l + {QItemSelectionModel::}{currentColumnChanged()} of another view's + \l {QItemSelectionModel}{selection model}. + + The following example illustrates how to update all widgets + with new data whenever the selection of a QTableView named + \c myTableView changes: + + \snippet doc/src/snippets/code/src_gui_itemviews_qdatawidgetmapper.cpp 2 + + \sa currentIndex() +*/ +void QDataWidgetMapper::setCurrentModelIndex(const QModelIndex &index) +{ + Q_D(QDataWidgetMapper); + + if (!index.isValid() + || index.model() != d->model + || index.parent() != d->rootIndex) + return; + + setCurrentIndex(d->orientation == Qt::Horizontal ? index.row() : index.column()); +} + +/*! + Clears all mappings. + + \sa addMapping(), removeMapping() + */ +void QDataWidgetMapper::clearMapping() +{ + Q_D(QDataWidgetMapper); + + while (!d->widgetMap.isEmpty()) { + QWidget *w = d->widgetMap.takeLast().widget; + if (w) + w->removeEventFilter(d->delegate); + } +} + +/*! + \property QDataWidgetMapper::orientation + \brief the orientation of the model + + If the orientation is Qt::Horizontal (the default), a widget is + mapped to a column of a data model. The widget will be populated + with the model's data from its mapped column and the row that + currentIndex() points at. + + Use Qt::Horizontal for tabular data that looks like this: + + \table + \row \o 1 \o Nokia Corporation and/or its subsidiary(-ies) \o Oslo + \row \o 2 \o Trolltech Pty \o Brisbane + \row \o 3 \o Trolltech Inc \o Silicon Valley + \row \o 4 \o Trolltech China \o Beijing + \row \o 5 \o Trolltech GmbH \o Berlin + \endtable + + If the orientation is set to Qt::Vertical, a widget is mapped to + a row. Calling setCurrentIndex() will change the current column. + The widget will be populates with the model's data from its + mapped row and the column that currentIndex() points at. + + Use Qt::Vertical for tabular data that looks like this: + + \table + \row \o 1 \o 2 \o 3 \o 4 \o 5 + \row \o Nokia Corporation and/or its subsidiary(-ies) \o Trolltech Pty \o Trolltech Inc \o Trolltech China \o Trolltech GmbH + \row \o Oslo \o Brisbane \o Silicon Valley \o Beijing \i Berlin + \endtable + + Changing the orientation clears all existing mappings. +*/ +void QDataWidgetMapper::setOrientation(Qt::Orientation orientation) +{ + Q_D(QDataWidgetMapper); + + if (d->orientation == orientation) + return; + + clearMapping(); + d->orientation = orientation; +} + +Qt::Orientation QDataWidgetMapper::orientation() const +{ + Q_D(const QDataWidgetMapper); + return d->orientation; +} + +/*! + \property QDataWidgetMapper::submitPolicy + \brief the current submit policy + + Changing the current submit policy will revert all widgets + to the current data from the model. +*/ +void QDataWidgetMapper::setSubmitPolicy(SubmitPolicy policy) +{ + Q_D(QDataWidgetMapper); + if (policy == d->submitPolicy) + return; + + revert(); + d->submitPolicy = policy; +} + +QDataWidgetMapper::SubmitPolicy QDataWidgetMapper::submitPolicy() const +{ + Q_D(const QDataWidgetMapper); + return d->submitPolicy; +} + +QT_END_NAMESPACE + +#include "moc_qdatawidgetmapper.cpp" + +#endif // QT_NO_DATAWIDGETMAPPER diff --git a/src/gui/itemviews/qdatawidgetmapper.h b/src/gui/itemviews/qdatawidgetmapper.h new file mode 100644 index 0000000..d04e881 --- /dev/null +++ b/src/gui/itemviews/qdatawidgetmapper.h @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDATAWIDGETMAPPER_H +#define QDATAWIDGETMAPPER_H + +#include "QtCore/qobject.h" + +#ifndef QT_NO_DATAWIDGETMAPPER + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QAbstractItemDelegate; +class QAbstractItemModel; +class QModelIndex; +class QDataWidgetMapperPrivate; + +class Q_GUI_EXPORT QDataWidgetMapper: public QObject +{ + Q_OBJECT + + Q_ENUMS(SubmitPolicy) + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) + Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation) + Q_PROPERTY(SubmitPolicy submitPolicy READ submitPolicy WRITE setSubmitPolicy) + +public: + QDataWidgetMapper(QObject *parent = 0); + ~QDataWidgetMapper(); + + void setModel(QAbstractItemModel *model); + QAbstractItemModel *model() const; + + void setItemDelegate(QAbstractItemDelegate *delegate); + QAbstractItemDelegate *itemDelegate() const; + + void setRootIndex(const QModelIndex &index); + QModelIndex rootIndex() const; + + void setOrientation(Qt::Orientation aOrientation); + Qt::Orientation orientation() const; + + enum SubmitPolicy { AutoSubmit, ManualSubmit }; + void setSubmitPolicy(SubmitPolicy policy); + SubmitPolicy submitPolicy() const; + + void addMapping(QWidget *widget, int section); + void addMapping(QWidget *widget, int section, const QByteArray &propertyName); + void removeMapping(QWidget *widget); + int mappedSection(QWidget *widget) const; + QByteArray mappedPropertyName(QWidget *widget) const; + QWidget *mappedWidgetAt(int section) const; + void clearMapping(); + + int currentIndex() const; + +public Q_SLOTS: + void revert(); + bool submit(); + + void toFirst(); + void toLast(); + void toNext(); + void toPrevious(); + virtual void setCurrentIndex(int index); + void setCurrentModelIndex(const QModelIndex &index); + +Q_SIGNALS: + void currentIndexChanged(int index); + +private: + Q_DECLARE_PRIVATE(QDataWidgetMapper) + Q_DISABLE_COPY(QDataWidgetMapper) + Q_PRIVATE_SLOT(d_func(), void _q_dataChanged(const QModelIndex &, const QModelIndex &)) + Q_PRIVATE_SLOT(d_func(), void _q_commitData(QWidget *)) + Q_PRIVATE_SLOT(d_func(), void _q_closeEditor(QWidget *, QAbstractItemDelegate::EndEditHint)) + Q_PRIVATE_SLOT(d_func(), void _q_modelDestroyed()) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_DATAWIDGETMAPPER +#endif + diff --git a/src/gui/itemviews/qdirmodel.cpp b/src/gui/itemviews/qdirmodel.cpp new file mode 100644 index 0000000..7da7c7a --- /dev/null +++ b/src/gui/itemviews/qdirmodel.cpp @@ -0,0 +1,1410 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdirmodel.h" + +#ifndef QT_NO_DIRMODEL +#include <qstack.h> +#include <qfile.h> +#include <qurl.h> +#include <qmime.h> +#include <qpair.h> +#include <qvector.h> +#include <qobject.h> +#include <qdatetime.h> +#include <qlocale.h> +#include <qstyle.h> +#include <qapplication.h> +#include <private/qabstractitemmodel_p.h> +#include <qdebug.h> + +/*! + \enum QDirModel::Roles + \value FileIconRole + \value FilePathRole + \value FileNameRole +*/ + +QT_BEGIN_NAMESPACE + +class QDirModelPrivate : public QAbstractItemModelPrivate +{ + Q_DECLARE_PUBLIC(QDirModel) + +public: + struct QDirNode + { + QDirNode() : parent(0), populated(false), stat(false) {} + ~QDirNode() { children.clear(); } + QDirNode *parent; + QFileInfo info; + QIcon icon; // cache the icon + mutable QVector<QDirNode> children; + mutable bool populated; // have we read the children + mutable bool stat; + }; + + QDirModelPrivate() + : resolveSymlinks(true), + readOnly(true), + lazyChildCount(false), + allowAppendChild(true), + iconProvider(&defaultProvider), + shouldStat(true) // ### This is set to false by QFileDialog + { } + + void init(); + QDirNode *node(int row, QDirNode *parent) const; + QVector<QDirNode> children(QDirNode *parent, bool stat) const; + + void _q_refresh(); + + void savePersistentIndexes(); + void restorePersistentIndexes(); + + QFileInfoList entryInfoList(const QString &path) const; + QStringList entryList(const QString &path) const; + + QString name(const QModelIndex &index) const; + QString size(const QModelIndex &index) const; + QString type(const QModelIndex &index) const; + QString time(const QModelIndex &index) const; + + void appendChild(QDirModelPrivate::QDirNode *parent, const QString &path) const; + static QFileInfo resolvedInfo(QFileInfo info); + + inline QDirNode *node(const QModelIndex &index) const; + inline void populate(QDirNode *parent) const; + inline void clear(QDirNode *parent) const; + + void invalidate(); + + mutable QDirNode root; + bool resolveSymlinks; + bool readOnly; + bool lazyChildCount; + bool allowAppendChild; + + QDir::Filters filters; + QDir::SortFlags sort; + QStringList nameFilters; + + QFileIconProvider *iconProvider; + QFileIconProvider defaultProvider; + + struct SavedPersistent { + QString path; + int column; + QPersistentModelIndexData *data; + QPersistentModelIndex index; + }; + QList<SavedPersistent> savedPersistent; + QPersistentModelIndex toBeRefreshed; + + bool shouldStat; // use the "carefull not to stat directories" mode +}; + +void qt_setDirModelShouldNotStat(QDirModelPrivate *modelPrivate) +{ + modelPrivate->shouldStat = false; +} + +QDirModelPrivate::QDirNode *QDirModelPrivate::node(const QModelIndex &index) const +{ + QDirModelPrivate::QDirNode *n = + static_cast<QDirModelPrivate::QDirNode*>(index.internalPointer()); + Q_ASSERT(n); + return n; +} + +void QDirModelPrivate::populate(QDirNode *parent) const +{ + Q_ASSERT(parent); + parent->children = children(parent, parent->stat); + parent->populated = true; +} + +void QDirModelPrivate::clear(QDirNode *parent) const +{ + Q_ASSERT(parent); + parent->children.clear(); + parent->populated = false; +} + +void QDirModelPrivate::invalidate() +{ + QStack<const QDirNode*> nodes; + nodes.push(&root); + while (!nodes.empty()) { + const QDirNode *current = nodes.pop(); + current->stat = false; + const QVector<QDirNode> children = current->children; + for (int i = 0; i < children.count(); ++i) + nodes.push(&children.at(i)); + } +} + +/*! + \class QDirModel + + \brief The QDirModel class provides a data model for the local filesystem. + + \ingroup model-view + + This class provides access to the local filesystem, providing functions + for renaming and removing files and directories, and for creating new + directories. In the simplest case, it can be used with a suitable display + widget as part of a browser or filer. + + QDirModel keeps a cache with file information. The cache needs to be + updated with refresh(). + + A directory model that displays the contents of a default directory + is usually constructed with a parent object: + + \snippet doc/src/snippets/shareddirmodel/main.cpp 2 + + A tree view can be used to display the contents of the model + + \snippet doc/src/snippets/shareddirmodel/main.cpp 4 + + and the contents of a particular directory can be displayed by + setting the tree view's root index: + + \snippet doc/src/snippets/shareddirmodel/main.cpp 7 + + The view's root index can be used to control how much of a + hierarchical model is displayed. QDirModel provides a convenience + function that returns a suitable model index for a path to a + directory within the model. + + QDirModel can be accessed using the standard interface provided by + QAbstractItemModel, but it also provides some convenience functions + that are specific to a directory model. The fileInfo() and isDir() + functions provide information about the underlying files and directories + related to items in the model. + + Directories can be created and removed using mkdir(), rmdir(), and the + model will be automatically updated to take the changes into account. + + \note QDirModel requires an instance of a GUI application. + + \sa nameFilters(), setFilter(), filter(), QListView, QTreeView, + {Dir View Example}, {Model Classes} +*/ + +/*! + Constructs a new directory model with the given \a parent. + Only those files matching the \a nameFilters and the + \a filters are included in the model. The sort order is given by the + \a sort flags. +*/ + +QDirModel::QDirModel(const QStringList &nameFilters, + QDir::Filters filters, + QDir::SortFlags sort, + QObject *parent) + : QAbstractItemModel(*new QDirModelPrivate, parent) +{ + Q_D(QDirModel); + // we always start with QDir::drives() + d->nameFilters = nameFilters.isEmpty() ? QStringList(QLatin1String("*")) : nameFilters; + d->filters = filters; + d->sort = sort; + d->root.parent = 0; + d->root.info = QFileInfo(); + d->clear(&d->root); +} + +/*! + Constructs a directory model with the given \a parent. +*/ + +QDirModel::QDirModel(QObject *parent) + : QAbstractItemModel(*new QDirModelPrivate, parent) +{ + Q_D(QDirModel); + d->init(); +} + +/*! + \internal +*/ +QDirModel::QDirModel(QDirModelPrivate &dd, QObject *parent) + : QAbstractItemModel(dd, parent) +{ + Q_D(QDirModel); + d->init(); +} + +/*! + Destroys this directory model. +*/ + +QDirModel::~QDirModel() +{ + +} + +/*! + Returns the model item index for the item in the \a parent with the + given \a row and \a column. + +*/ + +QModelIndex QDirModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_D(const QDirModel); + // note that rowCount does lazy population + if (column < 0 || column >= columnCount(parent) || row < 0 || parent.column() > 0) + return QModelIndex(); + // make sure the list of children is up to date + QDirModelPrivate::QDirNode *p = (d->indexValid(parent) ? d->node(parent) : &d->root); + Q_ASSERT(p); + if (!p->populated) + d->populate(p); // populate without stat'ing + if (row >= p->children.count()) + return QModelIndex(); + // now get the internal pointer for the index + QDirModelPrivate::QDirNode *n = d->node(row, d->indexValid(parent) ? p : 0); + Q_ASSERT(n); + + return createIndex(row, column, n); +} + +/*! + Return the parent of the given \a child model item. +*/ + +QModelIndex QDirModel::parent(const QModelIndex &child) const +{ + Q_D(const QDirModel); + + if (!d->indexValid(child)) + return QModelIndex(); + QDirModelPrivate::QDirNode *node = d->node(child); + QDirModelPrivate::QDirNode *par = (node ? node->parent : 0); + if (par == 0) // parent is the root node + return QModelIndex(); + + // get the parent's row + const QVector<QDirModelPrivate::QDirNode> children = + par->parent ? par->parent->children : d->root.children; + Q_ASSERT(children.count() > 0); + int row = (par - &(children.at(0))); + Q_ASSERT(row >= 0); + + return createIndex(row, 0, par); +} + +/*! + Returns the number of rows in the \a parent model item. + +*/ + +int QDirModel::rowCount(const QModelIndex &parent) const +{ + Q_D(const QDirModel); + if (parent.column() > 0) + return 0; + + if (!parent.isValid()) { + if (!d->root.populated) // lazy population + d->populate(&d->root); + return d->root.children.count(); + } + if (parent.model() != this) + return 0; + QDirModelPrivate::QDirNode *p = d->node(parent); + if (p->info.isDir() && !p->populated) // lazy population + d->populate(p); + return p->children.count(); +} + +/*! + Returns the number of columns in the \a parent model item. + +*/ + +int QDirModel::columnCount(const QModelIndex &parent) const +{ + if (parent.column() > 0) + return 0; + return 4; +} + +/*! + Returns the data for the model item \a index with the given \a role. +*/ +QVariant QDirModel::data(const QModelIndex &index, int role) const +{ + Q_D(const QDirModel); + if (!d->indexValid(index)) + return QVariant(); + + if (role == Qt::DisplayRole || role == Qt::EditRole) { + switch (index.column()) { + case 0: return d->name(index); + case 1: return d->size(index); + case 2: return d->type(index); + case 3: return d->time(index); + default: + qWarning("data: invalid display value column %d", index.column()); + return QVariant(); + } + } + + if (index.column() == 0) { + if (role == FileIconRole) + return fileIcon(index); + if (role == FilePathRole) + return filePath(index); + if (role == FileNameRole) + return fileName(index); + } + + if (index.column() == 1 && Qt::TextAlignmentRole == role) { + return Qt::AlignRight; + } + return QVariant(); +} + +/*! + Sets the data for the model item \a index with the given \a role to + the data referenced by the \a value. Returns true if successful; + otherwise returns false. + + \sa Qt::ItemDataRole +*/ + +bool QDirModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + Q_D(QDirModel); + if (!d->indexValid(index) || index.column() != 0 + || (flags(index) & Qt::ItemIsEditable) == 0 || role != Qt::EditRole) + return false; + + QDirModelPrivate::QDirNode *node = d->node(index); + QDir dir = node->info.dir(); + QString name = value.toString(); + if (dir.rename(node->info.fileName(), name)) { + node->info = QFileInfo(dir, name); + QModelIndex sibling = index.sibling(index.row(), 3); + emit dataChanged(index, sibling); + + d->toBeRefreshed = index.parent(); + QMetaObject::invokeMethod(this, "_q_refresh", Qt::QueuedConnection); + + return true; + } + + return false; +} + +/*! + Returns the data stored under the given \a role for the specified \a section + of the header with the given \a orientation. +*/ + +QVariant QDirModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal) { + if (role != Qt::DisplayRole) + return QVariant(); + switch (section) { + case 0: return tr("Name"); + case 1: return tr("Size"); + case 2: return +#ifdef Q_OS_MAC + tr("Kind", "Match OS X Finder"); +#else + tr("Type", "All other platforms"); +#endif + // Windows - Type + // OS X - Kind + // Konqueror - File Type + // Nautilus - Type + case 3: return tr("Date Modified"); + default: return QVariant(); + } + } + return QAbstractItemModel::headerData(section, orientation, role); +} + +/*! + Returns true if the \a parent model item has children; otherwise + returns false. +*/ + +bool QDirModel::hasChildren(const QModelIndex &parent) const +{ + Q_D(const QDirModel); + if (parent.column() > 0) + return false; + + if (!parent.isValid()) // the invalid index is the "My Computer" item + return true; // the drives + QDirModelPrivate::QDirNode *p = d->node(parent); + Q_ASSERT(p); + + if (d->lazyChildCount) // optimization that only checks for children if the node has been populated + return p->info.isDir(); + return p->info.isDir() && rowCount(parent) > 0; +} + +/*! + Returns the item flags for the given \a index in the model. + + \sa Qt::ItemFlags +*/ +Qt::ItemFlags QDirModel::flags(const QModelIndex &index) const +{ + Q_D(const QDirModel); + Qt::ItemFlags flags = QAbstractItemModel::flags(index); + if (!d->indexValid(index)) + return flags; + flags |= Qt::ItemIsDragEnabled; + if (d->readOnly) + return flags; + QDirModelPrivate::QDirNode *node = d->node(index); + if ((index.column() == 0) && node->info.isWritable()) { + flags |= Qt::ItemIsEditable; + if (fileInfo(index).isDir()) // is directory and is editable + flags |= Qt::ItemIsDropEnabled; + } + return flags; +} + +/*! + Sort the model items in the \a column using the \a order given. + The order is a value defined in \l Qt::SortOrder. +*/ + +void QDirModel::sort(int column, Qt::SortOrder order) +{ + QDir::SortFlags sort = QDir::DirsFirst | QDir::IgnoreCase; + if (order == Qt::DescendingOrder) + sort |= QDir::Reversed; + + switch (column) { + case 0: + sort |= QDir::Name; + break; + case 1: + sort |= QDir::Size; + break; + case 2: + sort |= QDir::Type; + break; + case 3: + sort |= QDir::Time; + break; + default: + break; + } + + setSorting(sort); +} + +/*! + Returns a list of MIME types that can be used to describe a list of items + in the model. +*/ + +QStringList QDirModel::mimeTypes() const +{ + return QStringList(QLatin1String("text/uri-list")); +} + +/*! + Returns an object that contains a serialized description of the specified + \a indexes. The format used to describe the items corresponding to the + indexes is obtained from the mimeTypes() function. + + If the list of indexes is empty, 0 is returned rather than a serialized + empty list. +*/ + +QMimeData *QDirModel::mimeData(const QModelIndexList &indexes) const +{ + QList<QUrl> urls; + QList<QModelIndex>::const_iterator it = indexes.begin(); + for (; it != indexes.end(); ++it) + if ((*it).column() == 0) + urls << QUrl::fromLocalFile(filePath(*it)); + QMimeData *data = new QMimeData(); + data->setUrls(urls); + return data; +} + +/*! + Handles the \a data supplied by a drag and drop operation that ended with + the given \a action over the row in the model specified by the \a row and + \a column and by the \a parent index. + + \sa supportedDropActions() +*/ + +bool QDirModel::dropMimeData(const QMimeData *data, Qt::DropAction action, + int /* row */, int /* column */, const QModelIndex &parent) +{ + Q_D(QDirModel); + if (!d->indexValid(parent) || isReadOnly()) + return false; + + bool success = true; + QString to = filePath(parent) + QDir::separator(); + QModelIndex _parent = parent; + + QList<QUrl> urls = data->urls(); + QList<QUrl>::const_iterator it = urls.constBegin(); + + switch (action) { + case Qt::CopyAction: + for (; it != urls.constEnd(); ++it) { + QString path = (*it).toLocalFile(); + success = QFile::copy(path, to + QFileInfo(path).fileName()) && success; + } + break; + case Qt::LinkAction: + for (; it != urls.constEnd(); ++it) { + QString path = (*it).toLocalFile(); + success = QFile::link(path, to + QFileInfo(path).fileName()) && success; + } + break; + case Qt::MoveAction: + for (; it != urls.constEnd(); ++it) { + QString path = (*it).toLocalFile(); + if (QFile::copy(path, to + QFileInfo(path).fileName()) + && QFile::remove(path)) { + QModelIndex idx=index(QFileInfo(path).path()); + if (idx.isValid()) { + refresh(idx); + //the previous call to refresh may invalidate the _parent. so recreate a new QModelIndex + _parent = index(to); + } + } else { + success = false; + } + } + break; + default: + return false; + } + + if (success) + refresh(_parent); + + return success; +} + +/*! + Returns the drop actions supported by this model. + + \sa Qt::DropActions +*/ + +Qt::DropActions QDirModel::supportedDropActions() const +{ + return Qt::CopyAction | Qt::MoveAction; // FIXME: LinkAction is not supported yet +} + +/*! + Sets the \a provider of file icons for the directory model. + +*/ + +void QDirModel::setIconProvider(QFileIconProvider *provider) +{ + Q_D(QDirModel); + d->iconProvider = provider; +} + +/*! + Returns the file icon provider for this directory model. +*/ + +QFileIconProvider *QDirModel::iconProvider() const +{ + Q_D(const QDirModel); + return d->iconProvider; +} + +/*! + Sets the name \a filters for the directory model. +*/ + +void QDirModel::setNameFilters(const QStringList &filters) +{ + Q_D(QDirModel); + d->nameFilters = filters; + emit layoutAboutToBeChanged(); + if (d->shouldStat) + refresh(QModelIndex()); + else + d->invalidate(); + emit layoutChanged(); +} + +/*! + Returns a list of filters applied to the names in the model. +*/ + +QStringList QDirModel::nameFilters() const +{ + Q_D(const QDirModel); + return d->nameFilters; +} + +/*! + Sets the directory model's filter to that specified by \a filters. + + Note that the filter you set should always include the QDir::AllDirs enum value, + otherwise QDirModel won't be able to read the directory structure. + + \sa QDir::Filters +*/ + +void QDirModel::setFilter(QDir::Filters filters) +{ + Q_D(QDirModel); + d->filters = filters; + emit layoutAboutToBeChanged(); + if (d->shouldStat) + refresh(QModelIndex()); + else + d->invalidate(); + emit layoutChanged(); +} + +/*! + Returns the filter specification for the directory model. + + \sa QDir::Filters +*/ + +QDir::Filters QDirModel::filter() const +{ + Q_D(const QDirModel); + return d->filters; +} + +/*! + Sets the directory model's sorting order to that specified by \a sort. + + \sa QDir::SortFlags +*/ + +void QDirModel::setSorting(QDir::SortFlags sort) +{ + Q_D(QDirModel); + d->sort = sort; + emit layoutAboutToBeChanged(); + if (d->shouldStat) + refresh(QModelIndex()); + else + d->invalidate(); + emit layoutChanged(); +} + +/*! + Returns the sorting method used for the directory model. + + \sa QDir::SortFlags */ + +QDir::SortFlags QDirModel::sorting() const +{ + Q_D(const QDirModel); + return d->sort; +} + +/*! + \property QDirModel::resolveSymlinks + \brief Whether the directory model should resolve symbolic links + + This is only relevant on operating systems that support symbolic + links. +*/ +void QDirModel::setResolveSymlinks(bool enable) +{ + Q_D(QDirModel); + d->resolveSymlinks = enable; +} + +bool QDirModel::resolveSymlinks() const +{ + Q_D(const QDirModel); + return d->resolveSymlinks; +} + +/*! + \property QDirModel::readOnly + \brief Whether the directory model allows writing to the file system + + If this property is set to false, the directory model will allow renaming, copying + and deleting of files and directories. + + This property is true by default +*/ + +void QDirModel::setReadOnly(bool enable) +{ + Q_D(QDirModel); + d->readOnly = enable; +} + +bool QDirModel::isReadOnly() const +{ + Q_D(const QDirModel); + return d->readOnly; +} + +/*! + \property QDirModel::lazyChildCount + \brief Whether the directory model optimizes the hasChildren function + to only check if the item is a directory. + + If this property is set to false, the directory model will make sure that a directory + actually containes any files before reporting that it has children. + Otherwise the directory model will report that an item has children if the item + is a directory. + + This property is false by default +*/ + +void QDirModel::setLazyChildCount(bool enable) +{ + Q_D(QDirModel); + d->lazyChildCount = enable; +} + +bool QDirModel::lazyChildCount() const +{ + Q_D(const QDirModel); + return d->lazyChildCount; +} + +/*! + QDirModel caches file information. This function updates the + cache. The \a parent parameter is the directory from which the + model is updated; the default value will update the model from + root directory of the file system (the entire model). +*/ + +void QDirModel::refresh(const QModelIndex &parent) +{ + Q_D(QDirModel); + + QDirModelPrivate::QDirNode *n = d->indexValid(parent) ? d->node(parent) : &(d->root); + + int rows = n->children.count(); + if (rows == 0) { + emit layoutAboutToBeChanged(); + n->stat = true; // make sure that next time we read all the info + n->populated = false; + emit layoutChanged(); + return; + } + + emit layoutAboutToBeChanged(); + d->savePersistentIndexes(); + d->rowsAboutToBeRemoved(parent, 0, rows - 1); + n->stat = true; // make sure that next time we read all the info + d->clear(n); + d->rowsRemoved(parent, 0, rows - 1); + d->restorePersistentIndexes(); + emit layoutChanged(); +} + +/*! + \overload + + Returns the model item index for the given \a path. +*/ + +QModelIndex QDirModel::index(const QString &path, int column) const +{ + Q_D(const QDirModel); + + if (path.isEmpty() || path == QCoreApplication::translate("QFileDialog", "My Computer")) + return QModelIndex(); + + QString absolutePath = QDir(path).absolutePath(); +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + absolutePath = absolutePath.toLower(); + // On Windows, "filename......." and "filename" are equivalent + if (absolutePath.endsWith(QLatin1Char('.'))) { + int i; + for (i = absolutePath.count() - 1; i >= 0; --i) { + if (absolutePath.at(i) != QLatin1Char('.')) + break; + } + absolutePath = absolutePath.left(i+1); + } +#endif + + QStringList pathElements = absolutePath.split(QLatin1Char('/'), QString::SkipEmptyParts); + if ((pathElements.isEmpty() || !QFileInfo(path).exists()) +#if !defined(Q_OS_WIN) || defined(Q_OS_WINCE) + && path != QLatin1String("/") +#endif + ) + return QModelIndex(); + + QModelIndex idx; // start with "My Computer" + if (!d->root.populated) // make sure the root is populated + d->populate(&d->root); + +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + if (absolutePath.startsWith(QLatin1String("//"))) { // UNC path + QString host = pathElements.first(); + int r = 0; + for (; r < d->root.children.count(); ++r) + if (d->root.children.at(r).info.fileName() == host) + break; + bool childAppended = false; + if (r >= d->root.children.count() && d->allowAppendChild) { + d->appendChild(&d->root, QLatin1String("//") + host); + childAppended = true; + } + idx = index(r, 0, QModelIndex()); + pathElements.pop_front(); + if (childAppended) + emit const_cast<QDirModel*>(this)->layoutChanged(); + } else if (pathElements.at(0).endsWith(QLatin1Char(':'))) { + pathElements[0] += QLatin1Char('/'); + } +#else + // add the "/" item, since it is a valid path element on unix + pathElements.prepend(QLatin1String("/")); +#endif + + for (int i = 0; i < pathElements.count(); ++i) { + Q_ASSERT(!pathElements.at(i).isEmpty()); + QString element = pathElements.at(i); + QDirModelPrivate::QDirNode *parent = (idx.isValid() ? d->node(idx) : &d->root); + + Q_ASSERT(parent); + if (!parent->populated) + d->populate(parent); + + // search for the element in the child nodes first + int row = -1; + for (int j = parent->children.count() - 1; j >= 0; --j) { + const QFileInfo& fi = parent->children.at(j).info; + QString childFileName; +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + childFileName = idx.isValid() ? fi.fileName() : fi.absoluteFilePath(); + childFileName = childFileName.toLower(); +#else + childFileName = idx.isValid() ? fi.fileName() : fi.absoluteFilePath(); +#endif + if (childFileName == element) { + if (i == pathElements.count() - 1) + parent->children[j].stat = true; + row = j; + break; + } + } + + // we couldn't find the path element, we create a new node since we _know_ that the path is valid + if (row == -1) { +#if defined(Q_OS_WINCE) + QString newPath; + if (parent->info.isRoot()) + newPath = parent->info.absoluteFilePath() + element; + else + newPath= parent->info.absoluteFilePath() + QLatin1Char('/') + element; +#else + QString newPath = parent->info.absoluteFilePath() + QLatin1Char('/') + element; +#endif + if (!d->allowAppendChild || !QFileInfo(newPath).isDir()) + return QModelIndex(); + d->appendChild(parent, newPath); + row = parent->children.count() - 1; + if (i == pathElements.count() - 1) // always stat children of the last element + parent->children[row].stat = true; + emit const_cast<QDirModel*>(this)->layoutChanged(); + } + + Q_ASSERT(row >= 0); + idx = createIndex(row, 0, static_cast<void*>(&parent->children[row])); + Q_ASSERT(idx.isValid()); + } + + if (column != 0) + return idx.sibling(idx.row(), column); + return idx; +} + +/*! + Returns true if the model item \a index represents a directory; + otherwise returns false. +*/ + +bool QDirModel::isDir(const QModelIndex &index) const +{ + Q_D(const QDirModel); + Q_ASSERT(d->indexValid(index)); + QDirModelPrivate::QDirNode *node = d->node(index); + return node->info.isDir(); +} + +/*! + Create a directory with the \a name in the \a parent model item. +*/ + +QModelIndex QDirModel::mkdir(const QModelIndex &parent, const QString &name) +{ + Q_D(QDirModel); + if (!d->indexValid(parent) || isReadOnly()) + return QModelIndex(); + + QDirModelPrivate::QDirNode *p = d->node(parent); + QString path = p->info.absoluteFilePath(); + // For the indexOf() method to work, the new directory has to be a direct child of + // the parent directory. + + QDir newDir(name); + QDir dir(path); + if (newDir.isRelative()) + newDir = QDir(path + QLatin1Char('/') + name); + QString childName = newDir.dirName(); // Get the singular name of the directory + newDir.cdUp(); + + if (newDir.absolutePath() != dir.absolutePath() || !dir.mkdir(name)) + return QModelIndex(); // nothing happened + + refresh(parent); + + QStringList entryList = d->entryList(path); + int r = entryList.indexOf(childName); + QModelIndex i = index(r, 0, parent); // return an invalid index + + return i; +} + +/*! + Removes the directory corresponding to the model item \a index in the + directory model and \bold{deletes the corresponding directory from the + file system}, returning true if successful. If the directory cannot be + removed, false is returned. + + \warning This function deletes directories from the file system; it does + \bold{not} move them to a location where they can be recovered. + + \sa remove() +*/ + +bool QDirModel::rmdir(const QModelIndex &index) +{ + Q_D(QDirModel); + if (!d->indexValid(index) || isReadOnly()) + return false; + + QDirModelPrivate::QDirNode *n = d_func()->node(index); + if (!n->info.isDir()) { + qWarning("rmdir: the node is not a directory"); + return false; + } + + QModelIndex par = parent(index); + QDirModelPrivate::QDirNode *p = d_func()->node(par); + QDir dir = p->info.dir(); // parent dir + QString path = n->info.absoluteFilePath(); + if (!dir.rmdir(path)) + return false; + + refresh(par); + + return true; +} + +/*! + Removes the model item \a index from the directory model and \bold{deletes the + corresponding file from the file system}, returning true if successful. If the + item cannot be removed, false is returned. + + \warning This function deletes files from the file system; it does \bold{not} + move them to a location where they can be recovered. + + \sa rmdir() +*/ + +bool QDirModel::remove(const QModelIndex &index) +{ + Q_D(QDirModel); + if (!d->indexValid(index) || isReadOnly()) + return false; + + QDirModelPrivate::QDirNode *n = d_func()->node(index); + if (n->info.isDir()) + return false; + + QModelIndex par = parent(index); + QDirModelPrivate::QDirNode *p = d_func()->node(par); + QDir dir = p->info.dir(); // parent dir + QString path = n->info.absoluteFilePath(); + if (!dir.remove(path)) + return false; + + refresh(par); + + return true; +} + +/*! + Returns the path of the item stored in the model under the + \a index given. + +*/ + +QString QDirModel::filePath(const QModelIndex &index) const +{ + Q_D(const QDirModel); + if (d->indexValid(index)) { + QFileInfo fi = fileInfo(index); + if (d->resolveSymlinks && fi.isSymLink()) + fi = d->resolvedInfo(fi); + return QDir::cleanPath(fi.absoluteFilePath()); + } + return QString(); // root path +} + +/*! + Returns the name of the item stored in the model under the + \a index given. + +*/ + +QString QDirModel::fileName(const QModelIndex &index) const +{ + Q_D(const QDirModel); + if (!d->indexValid(index)) + return QString(); + QFileInfo info = fileInfo(index); + if (info.isRoot()) + return info.absoluteFilePath(); + if (d->resolveSymlinks && info.isSymLink()) + info = d->resolvedInfo(info); + return info.fileName(); +} + +/*! + Returns the icons for the item stored in the model under the given + \a index. +*/ + +QIcon QDirModel::fileIcon(const QModelIndex &index) const +{ + Q_D(const QDirModel); + if (!d->indexValid(index)) + return d->iconProvider->icon(QFileIconProvider::Computer); + QDirModelPrivate::QDirNode *node = d->node(index); + if (node->icon.isNull()) + node->icon = d->iconProvider->icon(node->info); + return node->icon; +} + +/*! + Returns the file information for the specified model \a index. + + \bold{Note:} If the model index represents a symbolic link in the + underlying filing system, the file information returned will contain + information about the symbolic link itself, regardless of whether + resolveSymlinks is enabled or not. + + \sa QFileInfo::symLinkTarget() +*/ + +QFileInfo QDirModel::fileInfo(const QModelIndex &index) const +{ + Q_D(const QDirModel); + Q_ASSERT(d->indexValid(index)); + + QDirModelPrivate::QDirNode *node = d->node(index); + return node->info; +} + +/*! + \fn QObject *QDirModel::parent() const + \internal +*/ + +/* + The root node is never seen outside the model. +*/ + +void QDirModelPrivate::init() +{ + filters = QDir::AllEntries | QDir::NoDotAndDotDot; + sort = QDir::Name; + nameFilters << QLatin1String("*"); + root.parent = 0; + root.info = QFileInfo(); + clear(&root); +} + +QDirModelPrivate::QDirNode *QDirModelPrivate::node(int row, QDirNode *parent) const +{ + if (row < 0) + return 0; + + bool isDir = !parent || parent->info.isDir(); + QDirNode *p = (parent ? parent : &root); + if (isDir && !p->populated) + populate(p); // will also resolve symlinks + + if (row >= p->children.count()) { + qWarning("node: the row does not exist"); + return 0; + } + + return const_cast<QDirNode*>(&p->children.at(row)); +} + +QVector<QDirModelPrivate::QDirNode> QDirModelPrivate::children(QDirNode *parent, bool stat) const +{ + Q_ASSERT(parent); + QFileInfoList infoList; + if (parent == &root) { + parent = 0; + infoList = QDir::drives(); + } else if (parent->info.isDir()) { + //resolve directory links only if requested. + if (parent->info.isSymLink() && resolveSymlinks) { + QString link = parent->info.symLinkTarget(); + if (link.size() > 1 && link.at(link.size() - 1) == QDir::separator()) + link.chop(1); + if (stat) + infoList = entryInfoList(link); + else + infoList = QDir(link).entryInfoList(nameFilters, QDir::AllEntries | QDir::System); + } else { + if (stat) + infoList = entryInfoList(parent->info.absoluteFilePath()); + else + infoList = QDir(parent->info.absoluteFilePath()).entryInfoList(nameFilters, QDir::AllEntries | QDir::System); + } + } + + QVector<QDirNode> nodes(infoList.count()); + for (int i = 0; i < infoList.count(); ++i) { + QDirNode &node = nodes[i]; + node.parent = parent; + node.info = infoList.at(i); + node.populated = false; + node.stat = shouldStat; + } + + return nodes; +} + +void QDirModelPrivate::_q_refresh() +{ + Q_Q(QDirModel); + q->refresh(toBeRefreshed); + toBeRefreshed = QModelIndex(); +} + +void QDirModelPrivate::savePersistentIndexes() +{ + Q_Q(QDirModel); + savedPersistent.clear(); + foreach (QPersistentModelIndexData *data, persistent.indexes) { + SavedPersistent saved; + QModelIndex index = data->index; + saved.path = q->filePath(index); + saved.column = index.column(); + saved.data = data; + saved.index = index; + savedPersistent.append(saved); + } +} + +void QDirModelPrivate::restorePersistentIndexes() +{ + Q_Q(QDirModel); + bool allow = allowAppendChild; + allowAppendChild = false; + for (int i = 0; i < savedPersistent.count(); ++i) { + QPersistentModelIndexData *data = savedPersistent.at(i).data; + QString path = savedPersistent.at(i).path; + int column = savedPersistent.at(i).column; + QModelIndex idx = q->index(path, column); + if (idx != data->index || data->model == 0) { + //data->model may be equal to 0 if the model is getting destroyed + persistent.indexes.remove(data->index); + data->index = idx; + data->model = q; + if (idx.isValid()) + persistent.indexes.insert(idx, data); + } + } + savedPersistent.clear(); + allowAppendChild = allow; +} + +QFileInfoList QDirModelPrivate::entryInfoList(const QString &path) const +{ + const QDir dir(path); + return dir.entryInfoList(nameFilters, filters, sort); +} + +QStringList QDirModelPrivate::entryList(const QString &path) const +{ + const QDir dir(path); + return dir.entryList(nameFilters, filters, sort); +} + +QString QDirModelPrivate::name(const QModelIndex &index) const +{ + const QDirNode *n = node(index); + const QFileInfo info = n->info; + if (info.isRoot()) { + QString name = info.absoluteFilePath(); +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + if (name.startsWith(QLatin1Char('/'))) // UNC host + return info.fileName(); + if (name.endsWith(QLatin1Char('/'))) + name.chop(1); +#endif + return name; + } + return info.fileName(); +} + +QString QDirModelPrivate::size(const QModelIndex &index) const +{ + const QDirNode *n = node(index); + if (n->info.isDir()) { +#ifdef Q_OS_MAC + return QLatin1String("--"); +#else + return QLatin1String(""); +#endif + // Windows - "" + // OS X - "--" + // Konqueror - "4 KB" + // Nautilus - "9 items" (the number of children) + } + + // According to the Si standard KB is 1000 bytes, KiB is 1024 + // but on windows sizes are calulated by dividing by 1024 so we do what they do. + const quint64 kb = 1024; + const quint64 mb = 1024 * kb; + const quint64 gb = 1024 * mb; + const quint64 tb = 1024 * gb; + quint64 bytes = n->info.size(); + if (bytes >= tb) + return QLocale().toString(bytes / tb) + QString::fromLatin1(" TB"); + if (bytes >= gb) + return QLocale().toString(bytes / gb) + QString::fromLatin1(" GB"); + if (bytes >= mb) + return QLocale().toString(bytes / mb) + QString::fromLatin1(" MB"); + if (bytes >= kb) + return QLocale().toString(bytes / kb) + QString::fromLatin1(" KB"); + return QLocale().toString(bytes) + QString::fromLatin1(" bytes"); +} + +QString QDirModelPrivate::type(const QModelIndex &index) const +{ + return iconProvider->type(node(index)->info); +} + +QString QDirModelPrivate::time(const QModelIndex &index) const +{ +#ifndef QT_NO_DATESTRING + return node(index)->info.lastModified().toString(Qt::LocalDate); +#else + Q_UNUSED(index); + return QString(); +#endif +} + +void QDirModelPrivate::appendChild(QDirModelPrivate::QDirNode *parent, const QString &path) const +{ + QDirModelPrivate::QDirNode node; + node.populated = false; + node.stat = shouldStat; + node.parent = (parent == &root ? 0 : parent); + node.info = QFileInfo(path); + node.info.setCaching(true); + + // The following append(node) may reallocate the vector, thus + // we need to update the pointers to the childnodes parent. + QDirModelPrivate *that = const_cast<QDirModelPrivate *>(this); + that->savePersistentIndexes(); + parent->children.append(node); + for (int i = 0; i < parent->children.count(); ++i) { + QDirNode *childNode = &parent->children[i]; + for (int j = 0; j < childNode->children.count(); ++j) + childNode->children[j].parent = childNode; + } + that->restorePersistentIndexes(); +} + +QFileInfo QDirModelPrivate::resolvedInfo(QFileInfo info) +{ +#ifdef Q_OS_WIN + // On windows, we cannot create a shortcut to a shortcut. + return QFileInfo(info.symLinkTarget()); +#else + QStringList paths; + do { + QFileInfo link(info.symLinkTarget()); + if (link.isRelative()) + info.setFile(info.absolutePath(), link.filePath()); + else + info = link; + if (paths.contains(info.absoluteFilePath())) + return QFileInfo(); + paths.append(info.absoluteFilePath()); + } while (info.isSymLink()); + return info; +#endif +} + +QT_END_NAMESPACE + +#include "moc_qdirmodel.cpp" + +#endif // QT_NO_DIRMODEL diff --git a/src/gui/itemviews/qdirmodel.h b/src/gui/itemviews/qdirmodel.h new file mode 100644 index 0000000..ec1056f --- /dev/null +++ b/src/gui/itemviews/qdirmodel.h @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDIRMODEL_H +#define QDIRMODEL_H + +#include <QtCore/qabstractitemmodel.h> +#include <QtCore/qdir.h> +#include <QtGui/qfileiconprovider.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_DIRMODEL + +class QDirModelPrivate; + +class Q_GUI_EXPORT QDirModel : public QAbstractItemModel +{ + Q_OBJECT + Q_PROPERTY(bool resolveSymlinks READ resolveSymlinks WRITE setResolveSymlinks) + Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly) + Q_PROPERTY(bool lazyChildCount READ lazyChildCount WRITE setLazyChildCount) + +public: + enum Roles { + FileIconRole = Qt::DecorationRole, + FilePathRole = Qt::UserRole + 1, + FileNameRole + }; + + QDirModel(const QStringList &nameFilters, QDir::Filters filters, + QDir::SortFlags sort, QObject *parent = 0); + explicit QDirModel(QObject *parent = 0); + ~QDirModel(); + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &child) const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + bool hasChildren(const QModelIndex &index = QModelIndex()) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + + QStringList mimeTypes() const; + QMimeData *mimeData(const QModelIndexList &indexes) const; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent); + Qt::DropActions supportedDropActions() const; + + // QDirModel specific API + + void setIconProvider(QFileIconProvider *provider); + QFileIconProvider *iconProvider() const; + + void setNameFilters(const QStringList &filters); + QStringList nameFilters() const; + + void setFilter(QDir::Filters filters); + QDir::Filters filter() const; + + void setSorting(QDir::SortFlags sort); + QDir::SortFlags sorting() const; + + void setResolveSymlinks(bool enable); + bool resolveSymlinks() const; + + void setReadOnly(bool enable); + bool isReadOnly() const; + + void setLazyChildCount(bool enable); + bool lazyChildCount() const; + + QModelIndex index(const QString &path, int column = 0) const; + + bool isDir(const QModelIndex &index) const; + QModelIndex mkdir(const QModelIndex &parent, const QString &name); + bool rmdir(const QModelIndex &index); + bool remove(const QModelIndex &index); + + QString filePath(const QModelIndex &index) const; + QString fileName(const QModelIndex &index) const; + QIcon fileIcon(const QModelIndex &index) const; + QFileInfo fileInfo(const QModelIndex &index) const; + +#ifdef Q_NO_USING_KEYWORD + inline QObject *parent() const { return QObject::parent(); } +#else + using QObject::parent; +#endif + +public Q_SLOTS: + void refresh(const QModelIndex &parent = QModelIndex()); + +protected: + QDirModel(QDirModelPrivate &, QObject *parent = 0); + friend class QFileDialogPrivate; + +private: + Q_DECLARE_PRIVATE(QDirModel) + Q_DISABLE_COPY(QDirModel) + Q_PRIVATE_SLOT(d_func(), void _q_refresh()) +}; + +#endif // QT_NO_DIRMODEL + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QDIRMODEL_H diff --git a/src/gui/itemviews/qfileiconprovider.cpp b/src/gui/itemviews/qfileiconprovider.cpp new file mode 100644 index 0000000..ac62551 --- /dev/null +++ b/src/gui/itemviews/qfileiconprovider.cpp @@ -0,0 +1,449 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfileiconprovider.h" + +#ifndef QT_NO_FILEICONPROVIDER +#include <qstyle.h> +#include <qapplication.h> +#include <qdir.h> +#if defined(Q_WS_WIN) +#define _WIN32_IE 0x0500 +#include <objbase.h> +#include <private/qpixmapdata_p.h> +#include <qpixmapcache.h> +#elif defined(Q_WS_MAC) +#include <private/qt_mac_p.h> +#endif +#include <private/qfunctions_p.h> +#ifdef Q_OS_WINCE +#include <Commctrl.h> +#endif + +#ifndef SHGFI_ADDOVERLAYS +#define SHGFI_ADDOVERLAYS 0x000000020 +#endif + +QT_BEGIN_NAMESPACE + +/*! + \class QFileIconProvider + + \brief The QFileIconProvider class provides file icons for the QDirModel class. +*/ + +/*! + \enum QFileIconProvider::IconType + \value Computer + \value Desktop + \value Trashcan + \value Network + \value Drive + \value Folder + \value File +*/ + +class QFileIconProviderPrivate +{ + Q_DECLARE_PUBLIC(QFileIconProvider) + +public: + QFileIconProviderPrivate(); + QIcon getIcon(QStyle::StandardPixmap name) const; +#ifdef Q_WS_WIN + QIcon getWinIcon(const QFileInfo &fi) const; +#elif defined(Q_WS_MAC) + QIcon getMacIcon(const QFileInfo &fi) const; +#endif + QFileIconProvider *q_ptr; + QString homePath; + +private: + QIcon file; + QIcon fileLink; + QIcon directory; + QIcon directoryLink; + QIcon harddisk; + QIcon floppy; + QIcon cdrom; + QIcon ram; + QIcon network; + QIcon computer; + QIcon desktop; + QIcon trashcan; + QIcon generic; + QIcon home; +}; + +QFileIconProviderPrivate::QFileIconProviderPrivate() +{ + QStyle *style = QApplication::style(); + file = style->standardIcon(QStyle::SP_FileIcon); + directory = style->standardIcon(QStyle::SP_DirIcon); + fileLink = style->standardIcon(QStyle::SP_FileLinkIcon); + directoryLink = style->standardIcon(QStyle::SP_DirLinkIcon); + harddisk = style->standardIcon(QStyle::SP_DriveHDIcon); + floppy = style->standardIcon(QStyle::SP_DriveFDIcon); + cdrom = style->standardIcon(QStyle::SP_DriveCDIcon); + network = style->standardIcon(QStyle::SP_DriveNetIcon); + computer = style->standardIcon(QStyle::SP_ComputerIcon); + desktop = style->standardIcon(QStyle::SP_DesktopIcon); + trashcan = style->standardIcon(QStyle::SP_TrashIcon); + home = style->standardIcon(QStyle::SP_DirHomeIcon); + homePath = QDir::home().absolutePath(); +} + +QIcon QFileIconProviderPrivate::getIcon(QStyle::StandardPixmap name) const +{ + switch (name) { + case QStyle::SP_FileIcon: + return file; + case QStyle::SP_FileLinkIcon: + return fileLink; + case QStyle::SP_DirIcon: + return directory; + case QStyle::SP_DirLinkIcon: + return directoryLink; + case QStyle::SP_DriveHDIcon: + return harddisk; + case QStyle::SP_DriveFDIcon: + return floppy; + case QStyle::SP_DriveCDIcon: + return cdrom; + case QStyle::SP_DriveNetIcon: + return network; + case QStyle::SP_ComputerIcon: + return computer; + case QStyle::SP_DesktopIcon: + return desktop; + case QStyle::SP_TrashIcon: + return trashcan; + case QStyle::SP_DirHomeIcon: + return home; + default: + return QIcon(); + } + return QIcon(); +} + +/*! + Constructs a file icon provider. +*/ + +QFileIconProvider::QFileIconProvider() + : d_ptr(new QFileIconProviderPrivate) +{ +} + +/*! + Destroys the file icon provider. + +*/ + +QFileIconProvider::~QFileIconProvider() +{ + delete d_ptr; +} + +/*! + Returns an icon set for the given \a type. +*/ + +QIcon QFileIconProvider::icon(IconType type) const +{ + Q_D(const QFileIconProvider); + switch (type) { + case Computer: + return d->getIcon(QStyle::SP_ComputerIcon); + case Desktop: + return d->getIcon(QStyle::SP_DesktopIcon); + case Trashcan: + return d->getIcon(QStyle::SP_TrashIcon); + case Network: + return d->getIcon(QStyle::SP_DriveNetIcon); + case Drive: + return d->getIcon(QStyle::SP_DriveHDIcon); + case Folder: + return d->getIcon(QStyle::SP_DirIcon); + case File: + return d->getIcon(QStyle::SP_FileIcon); + default: + break; + }; + return QIcon(); +} + +#ifdef Q_WS_WIN +QIcon QFileIconProviderPrivate::getWinIcon(const QFileInfo &fileInfo) const +{ + QIcon retIcon; + QString fileExtension = fileInfo.suffix().toUpper(); + fileExtension.prepend( QLatin1String(".") ); + + QString key; + if (fileInfo.isFile() && !fileInfo.isExecutable() && !fileInfo.isSymLink()) + key = QLatin1String("qt_") + fileExtension; + + QPixmap pixmap; + if (!key.isEmpty()) { + QPixmapCache::find(key, pixmap); + } + + if (!pixmap.isNull()) { + retIcon.addPixmap(pixmap); + if (QPixmapCache::find(key + QLatin1Char('l'), pixmap)) + retIcon.addPixmap(pixmap); + return retIcon; + } + + /* We don't use the variable, but by storing it statically, we + * ensure CoInitialize is only called once. */ + static HRESULT comInit = CoInitialize(NULL); + Q_UNUSED(comInit); + + SHFILEINFO info; + unsigned long val = 0; + + //Get the small icon +#ifndef Q_OS_WINCE + val = SHGetFileInfo((const WCHAR *)QDir::toNativeSeparators(fileInfo.filePath()).utf16(), 0, &info, + sizeof(SHFILEINFO), SHGFI_ICON|SHGFI_SMALLICON|SHGFI_SYSICONINDEX|SHGFI_ADDOVERLAYS); +#else + val = SHGetFileInfo((const WCHAR *)QDir::toNativeSeparators(fileInfo.filePath()).utf16(), 0, &info, + sizeof(SHFILEINFO), SHGFI_SMALLICON|SHGFI_SYSICONINDEX); +#endif + if (val) { + if (fileInfo.isDir() && !fileInfo.isRoot()) { + //using the unique icon index provided by windows save us from duplicate keys + key = QString::fromLatin1("qt_dir_%1").arg(info.iIcon); + QPixmapCache::find(key, pixmap); + if (!pixmap.isNull()) { + retIcon.addPixmap(pixmap); + if (QPixmapCache::find(key + QLatin1Char('l'), pixmap)) + retIcon.addPixmap(pixmap); + DestroyIcon(info.hIcon); + return retIcon; + } + } + if (pixmap.isNull()) { +#ifndef Q_OS_WINCE + pixmap = convertHIconToPixmap(info.hIcon); +#else + pixmap = convertHIconToPixmap(ImageList_GetIcon((HIMAGELIST) val, info.iIcon, ILD_NORMAL)); +#endif + if (!pixmap.isNull()) { + retIcon.addPixmap(pixmap); + if (!key.isEmpty()) + QPixmapCache::insert(key, pixmap); + } + else { + qWarning("QFileIconProviderPrivate::getWinIcon() no small icon found"); + } + } + DestroyIcon(info.hIcon); + } + + //Get the big icon +#ifndef Q_OS_WINCE + val = SHGetFileInfo((const WCHAR *)QDir::toNativeSeparators(fileInfo.filePath()).utf16(), 0, &info, + sizeof(SHFILEINFO), SHGFI_ICON|SHGFI_LARGEICON|SHGFI_SYSICONINDEX|SHGFI_ADDOVERLAYS); +#else + val = SHGetFileInfo((const WCHAR *)QDir::toNativeSeparators(fileInfo.filePath()).utf16(), 0, &info, + sizeof(SHFILEINFO), SHGFI_LARGEICON|SHGFI_SYSICONINDEX); +#endif + if (val) { + if (fileInfo.isDir() && !fileInfo.isRoot()) { + //using the unique icon index provided by windows save us from duplicate keys + key = QString::fromLatin1("qt_dir_%1").arg(info.iIcon); + } +#ifndef Q_OS_WINCE + pixmap = convertHIconToPixmap(info.hIcon); +#else + pixmap = convertHIconToPixmap(ImageList_GetIcon((HIMAGELIST) val, info.iIcon, ILD_NORMAL), true); +#endif + if (!pixmap.isNull()) { + retIcon.addPixmap(pixmap); + if (!key.isEmpty()) + QPixmapCache::insert(key + QLatin1Char('l'), pixmap); + } + else { + qWarning("QFileIconProviderPrivate::getWinIcon() no large icon found"); + } + DestroyIcon(info.hIcon); + } + return retIcon; +} + +#elif defined(Q_WS_MAC) +QIcon QFileIconProviderPrivate::getMacIcon(const QFileInfo &fi) const +{ + QIcon retIcon; + FSRef macRef; + OSStatus status = FSPathMakeRef(reinterpret_cast<const UInt8*>(fi.canonicalFilePath().toUtf8().constData()), + &macRef, 0); + if (status != noErr) + return retIcon; + FSCatalogInfo info; + HFSUniStr255 macName; + status = FSGetCatalogInfo(&macRef, kIconServicesCatalogInfoMask, &info, &macName, 0, 0); + if (status != noErr) + return retIcon; + IconRef iconRef; + SInt16 iconLabel; + status = GetIconRefFromFileInfo(&macRef, macName.length, macName.unicode, kIconServicesCatalogInfoMask, &info, kIconServicesNormalUsageFlag, &iconRef, &iconLabel); + if (status != noErr) + return retIcon; + extern void qt_mac_constructQIconFromIconRef(const IconRef, const IconRef, QIcon*, QStyle::StandardPixmap = QStyle::SP_CustomBase); // qmacstyle_mac.cpp + qt_mac_constructQIconFromIconRef(iconRef, 0, &retIcon); + ReleaseIconRef(iconRef); + return retIcon; +} +#endif + + +/*! + Returns an icon for the file described by \a info. +*/ + +QIcon QFileIconProvider::icon(const QFileInfo &info) const +{ + Q_D(const QFileIconProvider); +#ifdef Q_WS_MAC + QIcon retIcon = d->getMacIcon(info); + if (!retIcon.isNull()) + return retIcon; +#elif defined Q_WS_WIN + QIcon icon = d->getWinIcon(info); + if (!icon.isNull()) + return icon; +#endif + if (info.isRoot()) +#if defined (Q_WS_WIN) && !defined(Q_OS_WINCE) + { + uint type = DRIVE_UNKNOWN; + QT_WA({ type = GetDriveTypeW((wchar_t *)info.absoluteFilePath().utf16()); }, + { type = GetDriveTypeA(info.absoluteFilePath().toLocal8Bit()); }); + + switch (type) { + case DRIVE_REMOVABLE: + return d->getIcon(QStyle::SP_DriveFDIcon); + case DRIVE_FIXED: + return d->getIcon(QStyle::SP_DriveHDIcon); + case DRIVE_REMOTE: + return d->getIcon(QStyle::SP_DriveNetIcon); + case DRIVE_CDROM: + return d->getIcon(QStyle::SP_DriveCDIcon); + case DRIVE_RAMDISK: + case DRIVE_UNKNOWN: + case DRIVE_NO_ROOT_DIR: + default: + return d->getIcon(QStyle::SP_DriveHDIcon); + } + } +#else + return d->getIcon(QStyle::SP_DriveHDIcon); +#endif + if (info.isFile()) { + if (info.isSymLink()) + return d->getIcon(QStyle::SP_FileLinkIcon); + else + return d->getIcon(QStyle::SP_FileIcon); + } + if (info.isDir()) { + if (info.isSymLink()) { + return d->getIcon(QStyle::SP_DirLinkIcon); + } else { + if (info.absoluteFilePath() == d->homePath) { + return d->getIcon(QStyle::SP_DirHomeIcon); + } else { + return d->getIcon(QStyle::SP_DirIcon); + } + } + } + return QIcon(); +} + +/*! + Returns the type of the file described by \a info. +*/ + +QString QFileIconProvider::type(const QFileInfo &info) const +{ + if (info.isRoot()) + return QApplication::translate("QFileDialog", "Drive"); + if (info.isFile()) { + if (!info.suffix().isEmpty()) + return info.suffix() + QLatin1Char(' ') + QApplication::translate("QFileDialog", "File"); + return QApplication::translate("QFileDialog", "File"); + } + + if (info.isDir()) + return QApplication::translate("QFileDialog", +#ifdef Q_WS_WIN + "File Folder", "Match Windows Explorer" +#else + "Folder", "All other platforms" +#endif + ); + // Windows - "File Folder" + // OS X - "Folder" + // Konqueror - "Folder" + // Nautilus - "folder" + + if (info.isSymLink()) + return QApplication::translate("QFileDialog", +#ifdef Q_OS_MAC + "Alias", "Mac OS X Finder" +#else + "Shortcut", "All other platforms" +#endif + ); + // OS X - "Alias" + // Windows - "Shortcut" + // Konqueror - "Folder" or "TXT File" i.e. what it is pointing to + // Nautilus - "link to folder" or "link to object file", same as Konqueror + + return QApplication::translate("QFileDialog", "Unknown"); +} + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/itemviews/qfileiconprovider.h b/src/gui/itemviews/qfileiconprovider.h new file mode 100644 index 0000000..7928552 --- /dev/null +++ b/src/gui/itemviews/qfileiconprovider.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILEICONPROVIDER_H +#define QFILEICONPROVIDER_H + +#include <QtGui/qicon.h> +#include <QtCore/qfileinfo.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_FILEICONPROVIDER + +class QFileIconProviderPrivate; + +class Q_GUI_EXPORT QFileIconProvider +{ +public: + QFileIconProvider(); + virtual ~QFileIconProvider(); + enum IconType { Computer, Desktop, Trashcan, Network, Drive, Folder, File }; + virtual QIcon icon(IconType type) const; + virtual QIcon icon(const QFileInfo &info) const; + virtual QString type(const QFileInfo &info) const; + +private: + Q_DECLARE_PRIVATE(QFileIconProvider) + QFileIconProviderPrivate *d_ptr; + Q_DISABLE_COPY(QFileIconProvider) +}; + +#endif // QT_NO_FILEICONPROVIDER + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QFILEICONPROVIDER_H + diff --git a/src/gui/itemviews/qheaderview.cpp b/src/gui/itemviews/qheaderview.cpp new file mode 100644 index 0000000..5bd82d4 --- /dev/null +++ b/src/gui/itemviews/qheaderview.cpp @@ -0,0 +1,3558 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qheaderview.h" + +#ifndef QT_NO_ITEMVIEWS +#include <qbitarray.h> +#include <qbrush.h> +#include <qdebug.h> +#include <qevent.h> +#include <qpainter.h> +#include <qscrollbar.h> +#include <qtooltip.h> +#include <qwhatsthis.h> +#include <qstyle.h> +#include <qstyleoption.h> +#include <qvector.h> +#include <qapplication.h> +#include <qvarlengtharray.h> +#include <qabstractitemdelegate.h> +#include <qvariant.h> +#include <private/qheaderview_p.h> +#include <private/qabstractitemmodel_p.h> + +#ifndef QT_NO_DATASTREAM +#include <qdatastream.h> + +QT_BEGIN_NAMESPACE + +QDataStream &operator<<(QDataStream &out, const QHeaderViewPrivate::SectionSpan &span) +{ + span.write(out); + return out; +} + +QDataStream &operator>>(QDataStream &in, QHeaderViewPrivate::SectionSpan &span) +{ + span.read(in); + return in; +} +#endif + + +/*! + \class QHeaderView + + \brief The QHeaderView class provides a header row or header column for + item views. + + \ingroup model-view + \mainclass + + A QHeaderView displays the headers used in item views such as the + QTableView and QTreeView classes. It takes the place of Qt3's \c QHeader + class previously used for the same purpose, but uses the Qt's model/view + architecture for consistency with the item view classes. + + The QHeaderView class is one of the \l{Model/View Classes} and is part of + Qt's \l{Model/View Programming}{model/view framework}. + + The header gets the data for each section from the model using the + QAbstractItemModel::headerData() function. You can set the data by using + QAbstractItemModel::setHeaderData(). + + Each header has an orientation() and a number of sections, given by the + count() function. A section refers to a part of the header - either a row + or a column, depending on the orientation. + + Sections can be moved and resized using moveSection() and resizeSection(); + they can also be hidden and shown with hideSection() and showSection(). + + Each section of a header is described by a section ID, specified by its + section(), and can be located at a particular visualIndex() in the header. + A section can have a sort indicator set with setSortIndicator(); this + indicates whether the items in the associated item view will be sorted in + the order given by the section. + + For a horizontal header the section is equivalent to a column in the model, + and for a vertical header the section is equivalent to a row in the model. + + \section1 Moving Header Sections + + A header can be fixed in place, or made movable with setMovable(). It can + be made clickable with setClickable(), and has resizing behavior in + accordance with setResizeMode(). + + \note Double-clicking on a header to resize a section only applies for + visible rows. + + A header will emit sectionMoved() if the user moves a section, + sectionResized() if the user resizes a section, and sectionClicked() as + well as sectionHandleDoubleClicked() in response to mouse clicks. A header + will also emit sectionCountChanged() and sectionAutoResize(). + + You can identify a section using the logicalIndex() and logicalIndexAt() + functions, or by its index position, using the visualIndex() and + visualIndexAt() functions. The visual index will change if a section is + moved, but the logical index will not change. + + \section1 Appearance + + QTableWidget and QTableView create default headers. If you want + the headers to be visible, you can use \l{QFrame::}{setVisible()}. + + Not all \l{Qt::}{ItemDataRole}s will have an effect on a + QHeaderView. If you need to draw other roles, you can subclass + QHeaderView and reimplement \l{QHeaderView::}{paintEvent()}. + QHeaderView respects the following item data roles: + \l{Qt::}{TextAlignmentRole}, \l{Qt::}{DisplayRole}, + \l{Qt::}{FontRole}, \l{Qt::}{DecorationRole}, + \l{Qt::}{ForegroundRole}, and \l{Qt::}{BackgroundRole}. + + \note Each header renders the data for each section itself, and does not + rely on a delegate. As a result, calling a header's setItemDelegate() + function will have no effect. + + \sa {Model/View Programming}, QListView, QTableView, QTreeView +*/ + +/*! + \enum QHeaderView::ResizeMode + + The resize mode specifies the behavior of the header sections. It can be + set on the entire header view or on individual sections using + setResizeMode(). + + \value Interactive The user can resize the section. The section can also be + resized programmatically using resizeSection(). The section size + defaults to \l defaultSectionSize. (See also + \l cascadingSectionResizes.) + + \value Fixed The user cannot resize the section. The section can only be + resized programmatically using resizeSection(). The section size + defaults to \l defaultSectionSize. + + \value Stretch QHeaderView will automatically resize the section to fill + the available space. The size cannot be changed by the user or + programmatically. + + \value ResizeToContents QHeaderView will automatically resize the section + to its optimal size based on the contents of the entire column or + row. The size cannot be changed by the user or programmatically. + (This value was introduced in 4.2) + + The following values are obsolete: + \value Custom Use Fixed instead. + + \sa setResizeMode() stretchLastSection minimumSectionSize +*/ + +/*! + \fn void QHeaderView::sectionMoved(int logicalIndex, int oldVisualIndex, + int newVisualIndex) + + This signal is emitted when a section is moved. The section's logical index + is specified by \a logicalIndex, the old index by \a oldVisualIndex, and + the new index position by \a newVisualIndex. + + \sa moveSection() +*/ + +/*! + \fn void QHeaderView::sectionResized(int logicalIndex, int oldSize, + int newSize) + + This signal is emitted when a section is resized. The section's logical + number is specified by \a logicalIndex, the old size by \a oldSize, and the + new size by \a newSize. + + \sa resizeSection() +*/ + +/*! + \fn void QHeaderView::sectionPressed(int logicalIndex) + + This signal is emitted when a section is pressed. The section's logical + index is specified by \a logicalIndex. + + \sa setClickable() +*/ + +/*! + \fn void QHeaderView::sectionClicked(int logicalIndex) + + This signal is emitted when a section is clicked. The section's logical + index is specified by \a logicalIndex. + + Note that the sectionPressed signal will also be emitted. + + \sa setClickable(), sectionPressed() +*/ + +/*! + \fn void QHeaderView::sectionEntered(int logicalIndex) + \since 4.3 + + This signal is emitted when the cursor moves over the section and the left + mouse button is pressed. The section's logical index is specified by + \a logicalIndex. + + \sa setClickable(), sectionPressed() +*/ + +/*! + \fn void QHeaderView::sectionDoubleClicked(int logicalIndex) + + This signal is emitted when a section is double-clicked. The section's + logical index is specified by \a logicalIndex. + + \sa setClickable() +*/ + +/*! + \fn void QHeaderView::sectionCountChanged(int oldCount, int newCount) + + This signal is emitted when the number of sections changes, i.e., when + sections are added or deleted. The original count is specified by + \a oldCount, and the new count by \a newCount. + + \sa count(), length(), headerDataChanged() +*/ + +/*! + \fn void QHeaderView::sectionHandleDoubleClicked(int logicalIndex) + + This signal is emitted when a section is double-clicked. The section's + logical index is specified by \a logicalIndex. + + \sa setClickable() +*/ + +/*! + \fn void QHeaderView::sortIndicatorChanged(int logicalIndex, + Qt::SortOrder order) + \since 4.3 + + This signal is emitted when the section containing the sort indicator or + the order indicated is changed. The section's logical index is specified + by \a logicalIndex and the sort order is specified by \a order. + + \sa setSortIndicator() +*/ + +/*! + \fn void QHeaderView::sectionAutoResize(int logicalIndex, + QHeaderView::ResizeMode mode) + + This signal is emitted when a section is automatically resized. The + section's logical index is specified by \a logicalIndex, and the resize + mode by \a mode. + + \sa setResizeMode(), stretchLastSection() +*/ +// ### Qt 5: change to sectionAutoResized() + +/*! + \fn void QHeaderView::geometriesChanged() + \since 4.2 + + This signal is emitted when the header's geometries have changed. +*/ + +/*! + \property QHeaderView::highlightSections + \brief whether the sections containing selected items are highlighted + + By default, this property is false. +*/ + +/*! + Creates a new generic header with the given \a orientation and \a parent. +*/ +QHeaderView::QHeaderView(Qt::Orientation orientation, QWidget *parent) + : QAbstractItemView(*new QHeaderViewPrivate, parent) +{ + Q_D(QHeaderView); + d->setDefaultValues(orientation); + initialize(); +} + +/*! + \internal +*/ +QHeaderView::QHeaderView(QHeaderViewPrivate &dd, + Qt::Orientation orientation, QWidget *parent) + : QAbstractItemView(dd, parent) +{ + Q_D(QHeaderView); + d->setDefaultValues(orientation); + initialize(); +} + +/*! + Destroys the header. +*/ + +QHeaderView::~QHeaderView() +{ +} + +/*! + \internal +*/ +void QHeaderView::initialize() +{ + Q_D(QHeaderView); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setFrameStyle(NoFrame); + setFocusPolicy(Qt::NoFocus); + d->viewport->setMouseTracking(true); + d->viewport->setBackgroundRole(QPalette::Button); + d->textElideMode = Qt::ElideNone; + delete d->itemDelegate; +} + +/*! + \reimp +*/ +void QHeaderView::setModel(QAbstractItemModel *model) +{ + if (model == this->model()) + return; + Q_D(QHeaderView); + if (d->model && d->model != QAbstractItemModelPrivate::staticEmptyModel()) { + if (d->orientation == Qt::Horizontal) { + QObject::disconnect(d->model, SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(sectionsInserted(QModelIndex,int,int))); + QObject::disconnect(d->model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(sectionsAboutToBeRemoved(QModelIndex,int,int))); + QObject::disconnect(d->model, SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sectionsRemoved(QModelIndex,int,int))); + } else { + QObject::disconnect(d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(sectionsInserted(QModelIndex,int,int))); + QObject::disconnect(d->model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(sectionsAboutToBeRemoved(QModelIndex,int,int))); + QObject::disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sectionsRemoved(QModelIndex,int,int))); + } + QObject::disconnect(d->model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), + this, SLOT(headerDataChanged(Qt::Orientation,int,int))); + QObject::disconnect(d->model, SIGNAL(layoutAboutToBeChanged()), + this, SLOT(_q_layoutAboutToBeChanged())); + } + + if (model && model != QAbstractItemModelPrivate::staticEmptyModel()) { + if (d->orientation == Qt::Horizontal) { + QObject::connect(model, SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(sectionsInserted(QModelIndex,int,int))); + QObject::connect(model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(sectionsAboutToBeRemoved(QModelIndex,int,int))); + QObject::connect(model, SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sectionsRemoved(QModelIndex,int,int))); + } else { + QObject::connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(sectionsInserted(QModelIndex,int,int))); + QObject::connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(sectionsAboutToBeRemoved(QModelIndex,int,int))); + QObject::connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sectionsRemoved(QModelIndex,int,int))); + } + QObject::connect(model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), + this, SLOT(headerDataChanged(Qt::Orientation,int,int))); + QObject::connect(model, SIGNAL(layoutAboutToBeChanged()), + this, SLOT(_q_layoutAboutToBeChanged())); + } + + d->state = QHeaderViewPrivate::NoClear; + QAbstractItemView::setModel(model); + d->state = QHeaderViewPrivate::NoState; + + // Users want to set sizes and modes before the widget is shown. + // Thus, we have to initialize when the model is set, + // and not lazily like we do in the other views. + initializeSections(); +} + +/*! + Returns the orientation of the header. + + \sa Qt::Orientation +*/ + +Qt::Orientation QHeaderView::orientation() const +{ + Q_D(const QHeaderView); + return d->orientation; +} + +/*! + Returns the offset of the header: this is the header's left-most (or + top-most for vertical headers) visible pixel. + + \sa setOffset() +*/ + +int QHeaderView::offset() const +{ + Q_D(const QHeaderView); + return d->offset; +} + +/*! + \fn void QHeaderView::setOffset(int offset) + + Sets the header's offset to \a offset. + + \sa offset(), length() +*/ + +void QHeaderView::setOffset(int newOffset) +{ + Q_D(QHeaderView); + if (d->offset == (int)newOffset) + return; + int ndelta = d->offset - newOffset; + d->offset = newOffset; + if (d->orientation == Qt::Horizontal) + d->viewport->scroll(isRightToLeft() ? -ndelta : ndelta, 0); + else + d->viewport->scroll(0, ndelta); + if (d->state == QHeaderViewPrivate::ResizeSection) { + QPoint cursorPos = QCursor::pos(); + if (d->orientation == Qt::Horizontal) + QCursor::setPos(cursorPos.x() + ndelta, cursorPos.y()); + else + QCursor::setPos(cursorPos.x(), cursorPos.y() + ndelta); + d->firstPos += ndelta; + d->lastPos += ndelta; + } +} + +/*! + \since 4.2 + Sets the offset to the start of the section at the given \a visualIndex. + + \sa setOffset(), sectionPosition() +*/ +void QHeaderView::setOffsetToSectionPosition(int visualIndex) +{ + Q_D(QHeaderView); + if (visualIndex > -1 && visualIndex < d->sectionCount) { + int position = d->headerSectionPosition(d->adjustedVisualIndex(visualIndex)); + setOffset(position); + } +} + +/*! + \since 4.2 + Sets the offset to make the last section visible. + + \sa setOffset(), sectionPosition(), setOffsetToSectionPosition() +*/ +void QHeaderView::setOffsetToLastSection() +{ + Q_D(const QHeaderView); + int size = (d->orientation == Qt::Horizontal ? viewport()->width() : viewport()->height()); + int position = length() - size; + setOffset(position); +} + +/*! + Returns the length along the orientation of the header. + + \sa sizeHint(), setResizeMode(), offset() +*/ + +int QHeaderView::length() const +{ + Q_D(const QHeaderView); + //Q_ASSERT(d->headerLength() == d->length); + return d->length; +} + +/*! + Returns a suitable size hint for this header. + + \sa sectionSizeHint() +*/ + +QSize QHeaderView::sizeHint() const +{ + Q_D(const QHeaderView); + if (count() < 1) + return QSize(0, 0); + if (d->cachedSizeHint.isValid()) + return d->cachedSizeHint; + int width = 0; + int height = 0; + // get size hint for the first n sections + int c = qMin(count(), 100); + for (int i = 0; i < c; ++i) { + if (isSectionHidden(i)) + continue; + QSize hint = sectionSizeFromContents(i); + width = qMax(hint.width(), width); + height = qMax(hint.height(), height); + } + // get size hint for the last n sections + c = qMax(count() - 100, c); + for (int j = count() - 1; j >= c; --j) { + if (isSectionHidden(j)) + continue; + QSize hint = sectionSizeFromContents(j); + width = qMax(hint.width(), width); + height = qMax(hint.height(), height); + } + d->cachedSizeHint = QSize(width, height); + return d->cachedSizeHint; +} + +/*! + Returns a suitable size hint for the section specified by \a logicalIndex. + + \sa sizeHint(), defaultSectionSize(), minimumSectionSize(), + Qt::SizeHintRole +*/ + +int QHeaderView::sectionSizeHint(int logicalIndex) const +{ + Q_D(const QHeaderView); + if (isSectionHidden(logicalIndex)) + return 0; + if (logicalIndex < 0 || logicalIndex >= count()) + return -1; + QSize size; + QVariant value = d->model->headerData(logicalIndex, d->orientation, Qt::SizeHintRole); + if (value.isValid()) + size = qvariant_cast<QSize>(value); + else + size = sectionSizeFromContents(logicalIndex); + int hint = d->orientation == Qt::Horizontal ? size.width() : size.height(); + return qMax(minimumSectionSize(), hint); +} + +/*! + Returns the visual index of the section that covers the given \a position + in the viewport. + + \sa logicalIndexAt() +*/ + +int QHeaderView::visualIndexAt(int position) const +{ + Q_D(const QHeaderView); + int vposition = position; + d->executePostedLayout(); + d->executePostedResize(); + const int count = d->sectionCount; + if (count < 1) + return -1; + + if (d->reverse()) + vposition = d->viewport->width() - vposition; + vposition += d->offset; + + if (vposition > d->length) + return -1; + int visual = d->headerVisualIndexAt(vposition); + if (visual < 0) + return -1; + + while (d->isVisualIndexHidden(visual)){ + ++visual; + if (visual >= count) + return -1; + } + return visual; +} + +/*! + Returns the section that covers the given \a position in the viewport. + + \sa visualIndexAt(), isSectionHidden() +*/ + +int QHeaderView::logicalIndexAt(int position) const +{ + const int visual = visualIndexAt(position); + if (visual > -1) + return logicalIndex(visual); + return -1; +} + +/*! + Returns the width (or height for vertical headers) of the given + \a logicalIndex. + + \sa length(), setResizeMode(), defaultSectionSize() +*/ + +int QHeaderView::sectionSize(int logicalIndex) const +{ + Q_D(const QHeaderView); + if (isSectionHidden(logicalIndex)) + return 0; + if (logicalIndex < 0 || logicalIndex >= count()) + return 0; + int visual = visualIndex(logicalIndex); + if (visual == -1) + return 0; + d->executePostedResize(); + return d->headerSectionSize(visual); +} + +/*! + Returns the section position of the given \a logicalIndex, or -1 if the + section is hidden. + + \sa sectionViewportPosition() +*/ + +int QHeaderView::sectionPosition(int logicalIndex) const +{ + Q_D(const QHeaderView); + int visual = visualIndex(logicalIndex); + // in some cases users may change the selections + // before we have a chance to do the layout + if (visual == -1) + return -1; + d->executePostedResize(); + return d->headerSectionPosition(visual); +} + +/*! + Returns the section viewport position of the given \a logicalIndex. + + If the section is hidden, the return value is undefined. + + \sa sectionPosition(), isSectionHidden() +*/ + +int QHeaderView::sectionViewportPosition(int logicalIndex) const +{ + Q_D(const QHeaderView); + if (logicalIndex >= count()) + return -1; + int position = sectionPosition(logicalIndex); + if (position < 0) + return position; // the section was hidden + int offsetPosition = position - d->offset; + if (d->reverse()) + return d->viewport->width() - (offsetPosition + sectionSize(logicalIndex)); + return offsetPosition; +} + +/*! + \fn int QHeaderView::logicalIndexAt(int x, int y) const + + Returns the logical index of the section at the given coordinate. If the + header is horizontal \a x will be used, otherwise \a y will be used to + find the logical index. +*/ + +/*! + \fn int QHeaderView::logicalIndexAt(const QPoint &pos) const + + Returns the logical index of the section at the position given in \a pos. + If the header is horizontal the x-coordinate will be used, otherwise the + y-coordinate will be used to find the logical index. + + \sa sectionPosition() +*/ + +/*! + Moves the section at visual index \a from to occupy visual index \a to. + + \sa sectionsMoved() +*/ + +void QHeaderView::moveSection(int from, int to) +{ + Q_D(QHeaderView); + + d->executePostedLayout(); + if (from < 0 || from >= d->sectionCount || to < 0 || to >= d->sectionCount) + return; + + if (from == to) { + int logical = logicalIndex(from); + Q_ASSERT(logical != -1); + updateSection(logical); + return; + } + + if (stretchLastSection() && to == d->lastVisibleVisualIndex()) + d->lastSectionSize = sectionSize(from); + + //int oldHeaderLength = length(); // ### for debugging; remove later + d->initializeIndexMapping(); + + QBitArray sectionHidden = d->sectionHidden; + int *visualIndices = d->visualIndices.data(); + int *logicalIndices = d->logicalIndices.data(); + int logical = logicalIndices[from]; + int visual = from; + + int affected_count = qAbs(to - from) + 1; + QVarLengthArray<int> sizes(affected_count); + QVarLengthArray<ResizeMode> modes(affected_count); + + // move sections and indices + if (to > from) { + sizes[to - from] = d->headerSectionSize(from); + modes[to - from] = d->headerSectionResizeMode(from); + while (visual < to) { + sizes[visual - from] = d->headerSectionSize(visual + 1); + modes[visual - from] = d->headerSectionResizeMode(visual + 1); + if (!sectionHidden.isEmpty()) + sectionHidden.setBit(visual, sectionHidden.testBit(visual + 1)); + visualIndices[logicalIndices[visual + 1]] = visual; + logicalIndices[visual] = logicalIndices[visual + 1]; + ++visual; + } + } else { + sizes[0] = d->headerSectionSize(from); + modes[0] = d->headerSectionResizeMode(from); + while (visual > to) { + sizes[visual - to] = d->headerSectionSize(visual - 1); + modes[visual - to] = d->headerSectionResizeMode(visual - 1); + if (!sectionHidden.isEmpty()) + sectionHidden.setBit(visual, sectionHidden.testBit(visual - 1)); + visualIndices[logicalIndices[visual - 1]] = visual; + logicalIndices[visual] = logicalIndices[visual - 1]; + --visual; + } + } + if (!sectionHidden.isEmpty()) { + sectionHidden.setBit(to, d->sectionHidden.testBit(from)); + d->sectionHidden = sectionHidden; + } + visualIndices[logical] = to; + logicalIndices[to] = logical; + + //Q_ASSERT(oldHeaderLength == length()); + // move sizes + // ### check for spans of section sizes here + if (to > from) { + for (visual = from; visual <= to; ++visual) { + int size = sizes[visual - from]; + ResizeMode mode = modes[visual - from]; + d->createSectionSpan(visual, visual, size, mode); + } + } else { + for (visual = to; visual <= from; ++visual) { + int size = sizes[visual - to]; + ResizeMode mode = modes[visual - to]; + d->createSectionSpan(visual, visual, size, mode); + } + } + //Q_ASSERT(d->headerLength() == length()); + //Q_ASSERT(oldHeaderLength == length()); + //Q_ASSERT(d->logicalIndices.count() == d->sectionCount); + + if (d->hasAutoResizeSections()) + d->doDelayedResizeSections(); + d->viewport->update(); + + emit sectionMoved(logical, from, to); +} + +/*! + \since 4.2 + Swaps the section at visual index \a first with the section at visual + index \a second. + + \sa moveSection() +*/ +void QHeaderView::swapSections(int first, int second) +{ + Q_D(QHeaderView); + + if (first == second) + return; + d->executePostedLayout(); + if (first < 0 || first >= d->sectionCount || second < 0 || second >= d->sectionCount) + return; + + int firstSize = d->headerSectionSize(first); + ResizeMode firstMode = d->headerSectionResizeMode(first); + int firstLogical = d->logicalIndex(first); + + int secondSize = d->headerSectionSize(second); + ResizeMode secondMode = d->headerSectionResizeMode(second); + int secondLogical = d->logicalIndex(second); + + d->createSectionSpan(second, second, firstSize, firstMode); + d->createSectionSpan(first, first, secondSize, secondMode); + + d->initializeIndexMapping(); + + d->visualIndices[firstLogical] = second; + d->logicalIndices[second] = firstLogical; + + d->visualIndices[secondLogical] = first; + d->logicalIndices[first] = secondLogical; + + if (!d->sectionHidden.isEmpty()) { + bool firstHidden = d->sectionHidden.testBit(first); + bool secondHidden = d->sectionHidden.testBit(second); + d->sectionHidden.setBit(first, secondHidden); + d->sectionHidden.setBit(second, firstHidden); + } + + d->viewport->update(); + emit sectionMoved(firstLogical, first, second); + emit sectionMoved(secondLogical, second, first); +} + +/*! + \fn void QHeaderView::resizeSection(int logicalIndex, int size) + + Resizes the section specified by \a logicalIndex to \a size measured in + pixels. + + \sa sectionResized(), resizeMode(), sectionSize() +*/ + +void QHeaderView::resizeSection(int logical, int size) +{ + Q_D(QHeaderView); + if (logical < 0 || logical >= count()) + return; + + if (isSectionHidden(logical)) { + d->hiddenSectionSize.insert(logical, size); + return; + } + + int visual = visualIndex(logical); + if (visual == -1) + return; + + int oldSize = d->headerSectionSize(visual); + if (oldSize == size) + return; + + d->executePostedLayout(); + d->invalidateCachedSizeHint(); + + if (stretchLastSection() && visual == d->lastVisibleVisualIndex()) + d->lastSectionSize = size; + + if (size != oldSize) + d->createSectionSpan(visual, visual, size, d->headerSectionResizeMode(visual)); + + int w = d->viewport->width(); + int h = d->viewport->height(); + int pos = sectionViewportPosition(logical); + QRect r; + if (d->orientation == Qt::Horizontal) + if (isRightToLeft()) + r.setRect(0, 0, pos + size, h); + else + r.setRect(pos, 0, w - pos, h); + else + r.setRect(0, pos, w, h - pos); + + if (d->hasAutoResizeSections()) { + d->doDelayedResizeSections(); + r = d->viewport->rect(); + } + d->viewport->update(r.normalized()); + emit sectionResized(logical, oldSize, size); +} + +/*! + Resizes the sections according to the given \a mode, ignoring the current + resize mode. + + \sa resizeMode(), sectionResized() +*/ + +void QHeaderView::resizeSections(QHeaderView::ResizeMode mode) +{ + Q_D(QHeaderView); + d->resizeSections(mode, true); +} + +/*! + \fn void QHeaderView::hideSection(int logicalIndex) + Hides the section specified by \a logicalIndex. + + \sa showSection(), isSectionHidden(), hiddenSectionCount(), + setSectionHidden() +*/ + +/*! + \fn void QHeaderView::showSection(int logicalIndex) + Shows the section specified by \a logicalIndex. + + \sa hideSection(), isSectionHidden(), hiddenSectionCount(), + setSectionHidden() +*/ + +/*! + Returns true if the section specified by \a logicalIndex is explicitly + hidden from the user; otherwise returns false. + + \sa hideSection(), showSection(), setSectionHidden(), hiddenSectionCount() +*/ + +bool QHeaderView::isSectionHidden(int logicalIndex) const +{ + Q_D(const QHeaderView); + d->executePostedLayout(); + if (logicalIndex >= d->sectionHidden.count() || logicalIndex < 0 || logicalIndex >= d->sectionCount) + return false; + int visual = visualIndex(logicalIndex); + Q_ASSERT(visual != -1); + return d->sectionHidden.testBit(visual); +} + +/*! + \since 4.1 + + Returns the number of sections in the header that has been hidden. + + \sa setSectionHidden(), isSectionHidden() +*/ +int QHeaderView::hiddenSectionCount() const +{ + Q_D(const QHeaderView); + return d->hiddenSectionSize.count(); +} + +/*! + If \a hide is true the section specified by \a logicalIndex is hidden; + otherwise the section is shown. + + \sa isSectionHidden(), hiddenSectionCount() +*/ + +void QHeaderView::setSectionHidden(int logicalIndex, bool hide) +{ + Q_D(QHeaderView); + if (logicalIndex < 0 || logicalIndex >= count()) + return; + + d->executePostedLayout(); + int visual = visualIndex(logicalIndex); + Q_ASSERT(visual != -1); + if (hide == d->isVisualIndexHidden(visual)) + return; + if (hide) { + int size = d->headerSectionSize(visual); + if (!d->hasAutoResizeSections()) + resizeSection(logicalIndex, 0); + d->hiddenSectionSize.insert(logicalIndex, size); + if (d->sectionHidden.count() < count()) + d->sectionHidden.resize(count()); + d->sectionHidden.setBit(visual, true); + if (d->hasAutoResizeSections()) + d->doDelayedResizeSections(); + } else { + int size = d->hiddenSectionSize.value(logicalIndex, d->defaultSectionSize); + d->hiddenSectionSize.remove(logicalIndex); + if (d->hiddenSectionSize.isEmpty()) { + d->sectionHidden.clear(); + } else { + Q_ASSERT(visual <= d->sectionHidden.count()); + d->sectionHidden.setBit(visual, false); + } + resizeSection(logicalIndex, size); + } +} + +/*! + Returns the number of sections in the header. + + \sa sectionCountChanged(), length() +*/ + +int QHeaderView::count() const +{ + Q_D(const QHeaderView); + //Q_ASSERT(d->sectionCount == d->headerSectionCount()); + // ### this may affect the lazy layout + d->executePostedLayout(); + return d->sectionCount; +} + +/*! + Returns the visual index position of the section specified by the given + \a logicalIndex, or -1 otherwise. + + Hidden sections still have valid visual indexes. + + \sa logicalIndex() +*/ + +int QHeaderView::visualIndex(int logicalIndex) const +{ + Q_D(const QHeaderView); + if (logicalIndex < 0) + return -1; + d->executePostedLayout(); + if (d->visualIndices.isEmpty()) { // nothing has been moved, so we have no mapping + if (logicalIndex < d->sectionCount) + return logicalIndex; + } else if (logicalIndex < d->visualIndices.count()) { + int visual = d->visualIndices.at(logicalIndex); + Q_ASSERT(visual < d->sectionCount); + return visual; + } + return -1; +} + +/*! + Returns the logicalIndex for the section at the given \a visualIndex + position, or -1 otherwise. + + \sa visualIndex(), sectionPosition() +*/ + +int QHeaderView::logicalIndex(int visualIndex) const +{ + Q_D(const QHeaderView); + if (visualIndex < 0 || visualIndex >= d->sectionCount) + return -1; + return d->logicalIndex(visualIndex); +} + +/*! + If \a movable is true, the header may be moved by the user; otherwise it + is fixed in place. + + \sa isMovable(), sectionMoved() +*/ + +// ### Qt 5: change to setSectionsMovable() +void QHeaderView::setMovable(bool movable) +{ + Q_D(QHeaderView); + d->movableSections = movable; +} + +/*! + Returns true if the header can be moved by the user; otherwise returns + false. + + \sa setMovable() +*/ + +// ### Qt 5: change to sectionsMovable() +bool QHeaderView::isMovable() const +{ + Q_D(const QHeaderView); + return d->movableSections; +} + +/*! + If \a clickable is true, the header will respond to single clicks. + + \sa isClickable(), sectionClicked(), sectionPressed(), + setSortIndicatorShown() +*/ + +// ### Qt 5: change to setSectionsClickable() +void QHeaderView::setClickable(bool clickable) +{ + Q_D(QHeaderView); + d->clickableSections = clickable; +} + +/*! + Returns true if the header is clickable; otherwise returns false. A + clickable header could be set up to allow the user to change the + representation of the data in the view related to the header. + + \sa setClickable() +*/ + +// ### Qt 5: change to sectionsClickable() +bool QHeaderView::isClickable() const +{ + Q_D(const QHeaderView); + return d->clickableSections; +} + +void QHeaderView::setHighlightSections(bool highlight) +{ + Q_D(QHeaderView); + d->highlightSelected = highlight; +} + +bool QHeaderView::highlightSections() const +{ + Q_D(const QHeaderView); + return d->highlightSelected; +} + +/*! + Sets the constraints on how the header can be resized to those described + by the given \a mode. + + \sa resizeMode(), length(), sectionResized(), sectionAutoResize() +*/ + +void QHeaderView::setResizeMode(ResizeMode mode) +{ + Q_D(QHeaderView); + initializeSections(); + d->stretchSections = (mode == Stretch ? count() : 0); + d->contentsSections = (mode == ResizeToContents ? count() : 0); + d->setGlobalHeaderResizeMode(mode); + if (d->hasAutoResizeSections()) + d->doDelayedResizeSections(); // section sizes may change as a result of the new mode +} + +/*! + \overload + + Sets the constraints on how the section specified by \a logicalIndex in + the header can be resized to those described by the given \a mode. + + \note This setting will be ignored for the last section if the stretchLastSection + property is set to true. This is the default for the horizontal headers provided + by QTreeView. + + \sa setStretchLastSection() +*/ + +// ### Qt 5: change to setSectionResizeMode() +void QHeaderView::setResizeMode(int logicalIndex, ResizeMode mode) +{ + Q_D(QHeaderView); + int visual = visualIndex(logicalIndex); + Q_ASSERT(visual != -1); + + ResizeMode old = d->headerSectionResizeMode(visual); + d->setHeaderSectionResizeMode(visual, mode); + + if (mode == Stretch && old != Stretch) + ++d->stretchSections; + else if (mode == ResizeToContents && old != ResizeToContents) + ++d->contentsSections; + else if (mode != Stretch && old == Stretch) + --d->stretchSections; + else if (mode != ResizeToContents && old == ResizeToContents) + --d->contentsSections; + + if (d->hasAutoResizeSections() && d->state == QHeaderViewPrivate::NoState) + d->doDelayedResizeSections(); // section sizes may change as a result of the new mode +} + +/*! + Returns the resize mode that applies to the section specified by the given + \a logicalIndex. + + \sa setResizeMode() +*/ + +QHeaderView::ResizeMode QHeaderView::resizeMode(int logicalIndex) const +{ + Q_D(const QHeaderView); + int visual = visualIndex(logicalIndex); + Q_ASSERT(visual != -1); + return d->visualIndexResizeMode(visual); +} + +/*! + \since 4.1 + + Returns the number of sections that are set to resize mode stretch. In + views, this can be used to see if the headerview needs to resize the + sections when the view's geometry changes. + + \sa stretchLastSection, resizeMode() +*/ + +int QHeaderView::stretchSectionCount() const +{ + Q_D(const QHeaderView); + return d->stretchSections; +} + +/*! + \property QHeaderView::showSortIndicator + \brief whether the sort indicator is shown + + By default, this property is false. + + \sa setClickable() +*/ + +void QHeaderView::setSortIndicatorShown(bool show) +{ + Q_D(QHeaderView); + if (d->sortIndicatorShown == show) + return; + + d->sortIndicatorShown = show; + + if (sortIndicatorSection() < 0 || sortIndicatorSection() > count()) + return; + + if (d->visualIndexResizeMode(sortIndicatorSection()) == ResizeToContents) + resizeSections(); + + d->viewport->update(); +} + +bool QHeaderView::isSortIndicatorShown() const +{ + Q_D(const QHeaderView); + return d->sortIndicatorShown; +} + +/*! + Sets the sort indicator for the section specified by the given + \a logicalIndex in the direction specified by \a order, and removes the + sort indicator from any other section that was showing it. + + \a logicalIndex may be -1, in which case no sort indicator will be shown + and the model will return to its natural, unsorted order. Note that not + all models support this and may even crash in this case. + + \sa sortIndicatorSection() sortIndicatorOrder() +*/ + +void QHeaderView::setSortIndicator(int logicalIndex, Qt::SortOrder order) +{ + Q_D(QHeaderView); + + // This is so that people can set the position of the sort indicator before the fill the model + int old = d->sortIndicatorSection; + d->sortIndicatorSection = logicalIndex; + d->sortIndicatorOrder = order; + + if (logicalIndex >= d->sectionCount) + return; // nothing to do + + if (old != logicalIndex + && ((logicalIndex >= 0 && resizeMode(logicalIndex) == ResizeToContents) + || old >= d->sectionCount || (old >= 0 && resizeMode(old) == ResizeToContents))) { + resizeSections(); + d->viewport->update(); + } else { + if (old >= 0 && old != logicalIndex) + updateSection(old); + if (logicalIndex >= 0) + updateSection(logicalIndex); + } + + emit sortIndicatorChanged(logicalIndex, order); +} + +/*! + Returns the logical index of the section that has a sort indicator. + By default this is section 0. + + \sa setSortIndicator() sortIndicatorOrder() setSortIndicatorShown() +*/ + +int QHeaderView::sortIndicatorSection() const +{ + Q_D(const QHeaderView); + return d->sortIndicatorSection; +} + +/*! + Returns the order for the sort indicator. If no section has a sort + indicator the return value of this function is undefined. + + \sa setSortIndicator() sortIndicatorSection() +*/ + +Qt::SortOrder QHeaderView::sortIndicatorOrder() const +{ + Q_D(const QHeaderView); + return d->sortIndicatorOrder; +} + +/*! + \property QHeaderView::stretchLastSection + \brief whether the last visible section in the header takes up all the + available space + + The default value is false. + + \note The horizontal headers provided by QTreeView are configured with this + property set to true, ensuring that the view does not waste any of the + space assigned to it for its header. If this value is set to true, this + property will override the resize mode set on the last section in the + header. + + \sa setResizeMode() +*/ +bool QHeaderView::stretchLastSection() const +{ + Q_D(const QHeaderView); + return d->stretchLastSection; +} + +void QHeaderView::setStretchLastSection(bool stretch) +{ + Q_D(QHeaderView); + d->stretchLastSection = stretch; + if (d->state != QHeaderViewPrivate::NoState) + return; + if (stretch) + resizeSections(); + else if (count()) + resizeSection(count() - 1, d->defaultSectionSize); +} + +/*! + \since 4.2 + \property QHeaderView::cascadingSectionResizes + \brief whether interactive resizing will be cascaded to the following + sections once the section being resized by the user has reached its + minimum size + + This property only affects sections that have \l Interactive as their + resize mode. + + The default value is false. + + \sa setResizeMode() +*/ +bool QHeaderView::cascadingSectionResizes() const +{ + Q_D(const QHeaderView); + return d->cascadingResizing; +} + +void QHeaderView::setCascadingSectionResizes(bool enable) +{ + Q_D(QHeaderView); + d->cascadingResizing = enable; +} + +/*! + \property QHeaderView::defaultSectionSize + \brief the default size of the header sections before resizing. + + This property only affects sections that have \l Interactive or \l Fixed + as their resize mode. + + \sa setResizeMode() minimumSectionSize +*/ +int QHeaderView::defaultSectionSize() const +{ + Q_D(const QHeaderView); + return d->defaultSectionSize; +} + +void QHeaderView::setDefaultSectionSize(int size) +{ + Q_D(QHeaderView); + d->defaultSectionSize = size; + d->forceInitializing = true; +} + +/*! + \since 4.2 + \property QHeaderView::minimumSectionSize + \brief the minimum size of the header sections. + + The minimum section size is the smallest section size allowed. If the + minimum section size is set to -1, QHeaderView will use the maximum of + the \l{QApplication::globalStrut()}{global strut} or the + \l{fontMetrics()}{font metrics} size. + + This property is honored by all \l{ResizeMode}{resize modes}. + + \sa setResizeMode() defaultSectionSize +*/ +int QHeaderView::minimumSectionSize() const +{ + Q_D(const QHeaderView); + if (d->minimumSectionSize == -1) { + QSize strut = QApplication::globalStrut(); + int margin = style()->pixelMetric(QStyle::PM_HeaderMargin, 0, this); + if (d->orientation == Qt::Horizontal) + return qMax(strut.width(), (fontMetrics().maxWidth() + margin)); + return qMax(strut.height(), (fontMetrics().lineSpacing() + margin)); + } + return d->minimumSectionSize; +} + +void QHeaderView::setMinimumSectionSize(int size) +{ + Q_D(QHeaderView); + d->minimumSectionSize = size; +} + +/*! + \since 4.1 + \property QHeaderView::defaultAlignment + \brief the default alignment of the text in each header section +*/ + +Qt::Alignment QHeaderView::defaultAlignment() const +{ + Q_D(const QHeaderView); + return d->defaultAlignment; +} + +void QHeaderView::setDefaultAlignment(Qt::Alignment alignment) +{ + Q_D(QHeaderView); + if (d->defaultAlignment == alignment) + return; + + d->defaultAlignment = alignment; + d->viewport->update(); +} + +/*! + \internal +*/ +void QHeaderView::doItemsLayout() +{ + initializeSections(); + QAbstractItemView::doItemsLayout(); +} + +/*! + Returns true if sections in the header has been moved; otherwise returns + false; + + \sa moveSection() +*/ +bool QHeaderView::sectionsMoved() const +{ + Q_D(const QHeaderView); + return !d->visualIndices.isEmpty(); +} + +/*! + \since 4.1 + + Returns true if sections in the header has been hidden; otherwise returns + false; + + \sa setSectionHidden() +*/ +bool QHeaderView::sectionsHidden() const +{ + Q_D(const QHeaderView); + return !d->hiddenSectionSize.isEmpty(); +} + +#ifndef QT_NO_DATASTREAM +/*! + \since 4.3 + + Saves the current state of this header view. + + To restore the saved state, pass the return value to restoreState(). + + \sa restoreState() +*/ +QByteArray QHeaderView::saveState() const +{ + Q_D(const QHeaderView); + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly); + stream << QHeaderViewPrivate::VersionMarker; + stream << 0; // current version is 0 + d->write(stream); + return data; +} + +/*! + \since 4.3 + Restores the \a state of this header view. + This function returns \c true if the state was restored; otherwise returns + false. + + \sa saveState() +*/ +bool QHeaderView::restoreState(const QByteArray &state) +{ + Q_D(QHeaderView); + if (state.isEmpty()) + return false; + QByteArray data = state; + QDataStream stream(&data, QIODevice::ReadOnly); + int marker; + int ver; + stream >> marker; + stream >> ver; + if (stream.status() != QDataStream::Ok + || marker != QHeaderViewPrivate::VersionMarker + || ver != 0) // current version is 0 + return false; + + if (d->read(stream)) { + emit sortIndicatorChanged(d->sortIndicatorSection, d->sortIndicatorOrder ); + d->viewport->update(); + return true; + } + return false; +} +#endif + +/*! + \reimp +*/ +void QHeaderView::reset() +{ + QAbstractItemView::reset(); + // it would be correct to call clear, but some apps rely + // on the header keeping the sections, even after calling reset + //d->clear(); + initializeSections(); +} + +/*! + Updates the changed header sections with the given \a orientation, from + \a logicalFirst to \a logicalLast inclusive. +*/ +void QHeaderView::headerDataChanged(Qt::Orientation orientation, int logicalFirst, int logicalLast) +{ + Q_D(QHeaderView); + if (d->orientation != orientation) + return; + + if (logicalFirst < 0 || logicalLast < 0 || logicalFirst >= count() || logicalLast >= count()) + return; + + d->invalidateCachedSizeHint(); + + int firstVisualIndex = INT_MAX, lastVisualIndex = -1; + + for (int section = logicalFirst; section <= logicalLast; ++section) { + const int visual = visualIndex(section); + firstVisualIndex = qMin(firstVisualIndex, visual); + lastVisualIndex = qMax(lastVisualIndex, visual); + } + + d->executePostedResize(); + const int first = d->headerSectionPosition(firstVisualIndex), + last = d->headerSectionPosition(lastVisualIndex) + + d->headerSectionSize(lastVisualIndex); + + if (orientation == Qt::Horizontal) { + d->viewport->update(first, 0, last - first, d->viewport->height()); + } else { + d->viewport->update(0, first, d->viewport->width(), last - first); + } +} + +/*! + \internal + \since 4.2 + + Updates the section specified by the given \a logicalIndex. +*/ + +void QHeaderView::updateSection(int logicalIndex) +{ + Q_D(QHeaderView); + if (d->orientation == Qt::Horizontal) + d->viewport->update(QRect(sectionViewportPosition(logicalIndex), + 0, sectionSize(logicalIndex), d->viewport->height())); + else + d->viewport->update(QRect(0, sectionViewportPosition(logicalIndex), + d->viewport->width(), sectionSize(logicalIndex))); +} + +/*! + Resizes the sections according to their size hints. Normally, you do not + have to call this function. +*/ + +void QHeaderView::resizeSections() +{ + Q_D(QHeaderView); + if (d->hasAutoResizeSections()) + d->resizeSections(Interactive, false); // no global resize mode +} + +/*! + This slot is called when sections are inserted into the \a parent. + \a logicalFirst and \a logicalLast indices signify where the new sections + were inserted. + + If only one section is inserted, \a logicalFirst and \a logicalLast will + be the same. +*/ + +void QHeaderView::sectionsInserted(const QModelIndex &parent, + int logicalFirst, int logicalLast) +{ + Q_D(QHeaderView); + if (parent != d->root) + return; // we only handle changes in the top level + int oldCount = d->sectionCount; + + d->invalidateCachedSizeHint(); + + // add the new sections + int insertAt = 0; + for (int spanStart = 0; insertAt < d->sectionSpans.count() && spanStart < logicalFirst; ++insertAt) + spanStart += d->sectionSpans.at(insertAt).count; + + int insertCount = logicalLast - logicalFirst + 1; + d->sectionCount += insertCount; + + if (d->sectionSpans.isEmpty() || insertAt >= d->sectionSpans.count()) { + int insertLength = d->defaultSectionSize * insertCount; + d->length += insertLength; + QHeaderViewPrivate::SectionSpan span(insertLength, insertCount, d->globalResizeMode); + d->sectionSpans.append(span); + } else if ((d->sectionSpans.at(insertAt).sectionSize() == d->defaultSectionSize) + && d->sectionSpans.at(insertAt).resizeMode == d->globalResizeMode) { + // add the new sections to an existing span + int insertLength = d->sectionSpans.at(insertAt).sectionSize() * insertCount; + d->length += insertLength; + d->sectionSpans[insertAt].size += insertLength; + d->sectionSpans[insertAt].count += insertCount; + } else { + // separate them out into their own span + int insertLength = d->defaultSectionSize * insertCount; + d->length += insertLength; + QHeaderViewPrivate::SectionSpan span(insertLength, insertCount, d->globalResizeMode); + d->sectionSpans.insert(insertAt, span); + } + + // update sorting column + if (d->sortIndicatorSection >= logicalFirst) + d->sortIndicatorSection += insertCount; + + // update resize mode section counts + if (d->globalResizeMode == Stretch) + d->stretchSections = d->sectionCount; + else if (d->globalResizeMode == ResizeToContents) + d->contentsSections = d->sectionCount; + + // clear selection cache + d->sectionSelected.clear(); + + // update mapping + if (!d->visualIndices.isEmpty() && !d->logicalIndices.isEmpty()) { + Q_ASSERT(d->visualIndices.count() == d->logicalIndices.count()); + int mappingCount = d->visualIndices.count(); + for (int i = 0; i < mappingCount; ++i) { + if (d->visualIndices.at(i) >= logicalFirst) + d->visualIndices[i] += insertCount; + if (d->logicalIndices.at(i) >= logicalFirst) + d->logicalIndices[i] += insertCount; + } + for (int j = logicalFirst; j <= logicalLast; ++j) { + d->visualIndices.insert(j, j); + d->logicalIndices.insert(j, j); + } + } + + // insert sections into sectionsHidden + if (!d->sectionHidden.isEmpty()) { + QBitArray sectionHidden(d->sectionHidden); + sectionHidden.resize(sectionHidden.count() + insertCount); + //sectionHidden.fill(false, logicalFirst, logicalLast + 1); + for (int i = logicalFirst; i <= logicalLast; ++i) + // visual == logical in this range (see previous block) + sectionHidden.setBit(i, false); + for (int j = logicalLast + 1; j < sectionHidden.count(); ++j) + sectionHidden.setBit(d->visualIndex(j), + d->sectionHidden.testBit(d->visualIndex(j - insertCount))); + d->sectionHidden = sectionHidden; + } + + // insert sections into hiddenSectionSize + QHash<int, int> newHiddenSectionSize; // from logical index to section size + for (int i = 0; i < logicalFirst; ++i) + if (isSectionHidden(i)) + newHiddenSectionSize[i] = d->hiddenSectionSize[i]; + for (int j = logicalLast + 1; j < d->sectionCount; ++j) + if (isSectionHidden(j)) + newHiddenSectionSize[j] = d->hiddenSectionSize[j - insertCount]; + d->hiddenSectionSize = newHiddenSectionSize; + + d->doDelayedResizeSections(); + emit sectionCountChanged(oldCount, count()); + + // if the new sections were not updated by resizing, we need to update now + if (!d->hasAutoResizeSections()) + d->viewport->update(); +} + +/*! + This slot is called when sections are removed from the \a parent. + \a logicalFirst and \a logicalLast signify where the sections were removed. + + If only one section is removed, \a logicalFirst and \a logicalLast will + be the same. +*/ + +void QHeaderView::sectionsAboutToBeRemoved(const QModelIndex &parent, + int logicalFirst, int logicalLast) +{ + Q_UNUSED(parent); + Q_UNUSED(logicalFirst); + Q_UNUSED(logicalLast); +} + +void QHeaderViewPrivate::updateHiddenSections(int logicalFirst, int logicalLast) +{ + Q_Q(QHeaderView); + const int changeCount = logicalLast - logicalFirst + 1; + + // remove sections from hiddenSectionSize + QHash<int, int> newHiddenSectionSize; // from logical index to section size + for (int i = 0; i < logicalFirst; ++i) + if (q->isSectionHidden(i)) + newHiddenSectionSize[i] = hiddenSectionSize[i]; + for (int j = logicalLast + 1; j < sectionCount; ++j) + if (q->isSectionHidden(j)) + newHiddenSectionSize[j - changeCount] = hiddenSectionSize[j]; + hiddenSectionSize = newHiddenSectionSize; + + // remove sections from sectionsHidden + if (!sectionHidden.isEmpty()) { + const int newsize = qMin(sectionCount - changeCount, sectionHidden.size()); + QBitArray newSectionHidden(newsize); + for (int j = 0, k = 0; j < sectionHidden.size(); ++j) { + const int logical = logicalIndex(j); + if (logical < logicalFirst || logical > logicalLast) { + newSectionHidden[k++] = sectionHidden[j]; + } + } + sectionHidden = newSectionHidden; + } +} + +void QHeaderViewPrivate::_q_sectionsRemoved(const QModelIndex &parent, + int logicalFirst, int logicalLast) +{ + Q_Q(QHeaderView); + if (parent != root) + return; // we only handle changes in the top level + if (qMin(logicalFirst, logicalLast) < 0 + || qMax(logicalLast, logicalFirst) >= sectionCount) + return; + int oldCount = q->count(); + int changeCount = logicalLast - logicalFirst + 1; + + updateHiddenSections(logicalFirst, logicalLast); + + if (visualIndices.isEmpty() && logicalIndices.isEmpty()) { + //Q_ASSERT(headerSectionCount() == sectionCount); + removeSectionsFromSpans(logicalFirst, logicalLast); + } else { + for (int l = logicalLast; l >= logicalFirst; --l) { + int visual = visualIndices.at(l); + for (int v = 0; v < sectionCount; ++v) { + if (v >= logicalIndices.count()) + continue; // the section doesn't exist + if (v > visual) { + int logical = logicalIndices.at(v); + --(visualIndices[logical]); + } + if (logicalIndex(v) > l) // no need to move the positions before l + --(logicalIndices[v]); + } + logicalIndices.remove(visual); + visualIndices.remove(l); + //Q_ASSERT(headerSectionCount() == sectionCount); + removeSectionsFromSpans(visual, visual); + } + // ### handle sectionSelection, sectionHidden + } + sectionCount -= changeCount; + + // update sorting column + if (sortIndicatorSection >= logicalFirst) { + if (sortIndicatorSection <= logicalLast) + sortIndicatorSection = -1; + else + sortIndicatorSection -= changeCount; + } + + // if we only have the last section (the "end" position) left, the header is empty + if (sectionCount <= 0) + clear(); + invalidateCachedSizeHint(); + emit q->sectionCountChanged(oldCount, q->count()); + viewport->update(); +} + +void QHeaderViewPrivate::_q_layoutAboutToBeChanged() +{ + //if there is no row/column we can't have mapping for columns + //because no QModelIndex in the model would be valid + // ### this is far from being bullet-proof and we would need a real system to + // ### map columns or rows persistently + if ((orientation == Qt::Horizontal && model->rowCount(root) == 0) + || model->columnCount(root) == 0) + return; + + for (int i = 0; i < sectionHidden.count(); ++i) + if (sectionHidden.testBit(i)) // ### note that we are using column or row 0 + persistentHiddenSections.append(orientation == Qt::Horizontal + ? model->index(0, logicalIndex(i), root) + : model->index(logicalIndex(i), 0, root)); +} + +void QHeaderViewPrivate::_q_layoutChanged() +{ + Q_Q(QHeaderView); + viewport->update(); + if (persistentHiddenSections.isEmpty() || modelIsEmpty()) { + if (modelSectionCount() != sectionCount) + q->initializeSections(); + persistentHiddenSections.clear(); + return; + } + bool sectionCountChanged = false; + for (int i = 0; i < sectionHidden.count(); ++i) { + if (sectionHidden.testBit(i)) + q->setSectionHidden(logicalIndex(i), false); + } + + for (int i = 0; i < persistentHiddenSections.count(); ++i) { + QModelIndex index = persistentHiddenSections.at(i); + if (index.isValid()) { + const int logical = (orientation == Qt::Horizontal + ? index.column() + : index.row()); + q->setSectionHidden(logical, true); + } else if (!sectionCountChanged && (modelSectionCount() != sectionCount)) { + sectionCountChanged = true; + break; + } + } + persistentHiddenSections.clear(); + + // the number of sections changed; we need to reread the state of the model + if (sectionCountChanged) + q->initializeSections(); +} + +/*! + \internal +*/ + +void QHeaderView::initializeSections() +{ + Q_D(QHeaderView); + const int oldCount = d->sectionCount; + const int newCount = d->modelSectionCount(); + if (newCount <= 0) { + d->clear(); + emit sectionCountChanged(oldCount, 0); + } else if (newCount != oldCount) { + const int min = qBound(0, oldCount, newCount - 1); + initializeSections(min, newCount - 1); + if (stretchLastSection()) // we've already gotten the size hint + d->lastSectionSize = sectionSize(logicalIndex(d->sectionCount - 1)); + + //make sure we update the hidden sections + if (newCount < oldCount) + d->updateHiddenSections(0, newCount-1); + } else if (d->forceInitializing) { + initializeSections(0, newCount - 1); + d->forceInitializing = false; + } +} + +/*! + \internal +*/ + +void QHeaderView::initializeSections(int start, int end) +{ + Q_D(QHeaderView); + + Q_ASSERT(start >= 0); + Q_ASSERT(end >= 0); + + d->executePostedLayout(); + d->invalidateCachedSizeHint(); + + if (end + 1 < d->sectionCount) { + int newCount = end + 1; + d->removeSectionsFromSpans(newCount, d->sectionCount); + if (!d->hiddenSectionSize.isEmpty()) { + if (d->sectionCount - newCount > d->hiddenSectionSize.count()) { + for (int i = end + 1; i < d->sectionCount; ++i) + d->hiddenSectionSize.remove(i); + } else { + QHash<int, int>::iterator it = d->hiddenSectionSize.begin(); + while (it != d->hiddenSectionSize.end()) { + if (it.key() > end) + it = d->hiddenSectionSize.erase(it); + else + ++it; + } + } + } + } + + int oldCount = d->sectionCount; + d->sectionCount = end + 1; + + if (!d->logicalIndices.isEmpty()) { + d->logicalIndices.resize(d->sectionCount); + d->visualIndices.resize(d->sectionCount); + for (int i = start; i < d->sectionCount; ++i){ + d->logicalIndices[i] = i; + d->visualIndices[i] = i; + } + } + + if (d->globalResizeMode == Stretch) + d->stretchSections = d->sectionCount; + else if (d->globalResizeMode == ResizeToContents) + d->contentsSections = d->sectionCount; + if (!d->sectionHidden.isEmpty()) + d->sectionHidden.resize(d->sectionCount); + + if (d->sectionCount > oldCount || d->forceInitializing) + d->createSectionSpan(start, end, (end - start + 1) * d->defaultSectionSize, d->globalResizeMode); + //Q_ASSERT(d->headerLength() == d->length); + + if (d->sectionCount != oldCount) + emit sectionCountChanged(oldCount, d->sectionCount); + d->viewport->update(); +} + +/*! + \reimp +*/ + +void QHeaderView::currentChanged(const QModelIndex ¤t, const QModelIndex &old) +{ + Q_D(QHeaderView); + + if (d->orientation == Qt::Horizontal && current.column() != old.column()) { + if (old.isValid() && old.parent() == d->root) + d->setDirtyRegion(QRect(sectionViewportPosition(old.column()), 0, + sectionSize(old.column()), d->viewport->height())); + if (current.isValid() && current.parent() == d->root) + d->setDirtyRegion(QRect(sectionViewportPosition(current.column()), 0, + sectionSize(current.column()), d->viewport->height())); + } else if (d->orientation == Qt::Vertical && current.row() != old.row()) { + if (old.isValid() && old.parent() == d->root) + d->setDirtyRegion(QRect(0, sectionViewportPosition(old.row()), + d->viewport->width(), sectionSize(old.row()))); + if (current.isValid() && current.parent() == d->root) + d->setDirtyRegion(QRect(0, sectionViewportPosition(current.row()), + d->viewport->width(), sectionSize(current.row()))); + } + d->updateDirtyRegion(); +} + + +/*! + \reimp +*/ + +bool QHeaderView::event(QEvent *e) +{ + Q_D(QHeaderView); + switch (e->type()) { + case QEvent::HoverEnter: { + QHoverEvent *he = static_cast<QHoverEvent*>(e); + d->hover = logicalIndexAt(he->pos()); + if (d->hover != -1) + updateSection(d->hover); + break; } + case QEvent::Leave: + case QEvent::HoverLeave: { + if (d->hover != -1) + updateSection(d->hover); + d->hover = -1; + break; } + case QEvent::HoverMove: { + QHoverEvent *he = static_cast<QHoverEvent*>(e); + int oldHover = d->hover; + d->hover = logicalIndexAt(he->pos()); + if (d->hover != oldHover) { + if (oldHover != -1) + updateSection(oldHover); + if (d->hover != -1) + updateSection(d->hover); + } + break; } + case QEvent::Timer: { // ### reimplement timerEvent() instead ? + QTimerEvent *te = static_cast<QTimerEvent*>(e); + if (te->timerId() == d->delayedResize.timerId()) { + d->delayedResize.stop(); + resizeSections(); + } + break; } + default: + break; + } + return QAbstractItemView::event(e); +} + +/*! + \reimp +*/ + +void QHeaderView::paintEvent(QPaintEvent *e) +{ + Q_D(QHeaderView); + + if (count() == 0) + return; + + QPainter painter(d->viewport); + const QPoint offset = d->scrollDelayOffset; + QRect translatedEventRect = e->rect(); + translatedEventRect.translate(offset); + + int start = -1; + int end = -1; + if (d->orientation == Qt::Horizontal) { + start = visualIndexAt(translatedEventRect.left()); + end = visualIndexAt(translatedEventRect.right()); + } else { + start = visualIndexAt(translatedEventRect.top()); + end = visualIndexAt(translatedEventRect.bottom()); + } + + if (d->reverse()) { + start = (start == -1 ? count() - 1 : start); + end = (end == -1 ? 0 : end); + } else { + start = (start == -1 ? 0 : start); + end = (end == -1 ? count() - 1 : end); + } + + int tmp = start; + start = qMin(start, end); + end = qMax(tmp, end); + + d->prepareSectionSelected(); // clear and resize the bit array + + QRect currentSectionRect; + int logical; + const int width = d->viewport->width(); + const int height = d->viewport->height(); + for (int i = start; i <= end; ++i) { + if (d->isVisualIndexHidden(i)) + continue; + painter.save(); + logical = logicalIndex(i); + if (d->orientation == Qt::Horizontal) { + currentSectionRect.setRect(sectionViewportPosition(logical), 0, sectionSize(logical), height); + } else { + currentSectionRect.setRect(0, sectionViewportPosition(logical), width, sectionSize(logical)); + } + currentSectionRect.translate(offset); + + QVariant variant = d->model->headerData(logical, d->orientation, + Qt::FontRole); + if (variant.isValid() && qVariantCanConvert<QFont>(variant)) { + QFont sectionFont = qvariant_cast<QFont>(variant); + painter.setFont(sectionFont); + } + paintSection(&painter, currentSectionRect, logical); + painter.restore(); + } + + QStyleOption opt; + opt.init(this); + // Paint the area beyond where there are indexes + if (d->reverse()) { + opt.state |= QStyle::State_Horizontal; + if (currentSectionRect.left() > translatedEventRect.left()) { + opt.rect = QRect(translatedEventRect.left(), 0, + currentSectionRect.left() - translatedEventRect.left(), height); + style()->drawControl(QStyle::CE_HeaderEmptyArea, &opt, &painter, this); + } + } else if (currentSectionRect.right() < translatedEventRect.right()) { + // paint to the right + opt.state |= QStyle::State_Horizontal; + opt.rect = QRect(currentSectionRect.right() + 1, 0, + translatedEventRect.right() - currentSectionRect.right(), height); + style()->drawControl(QStyle::CE_HeaderEmptyArea, &opt, &painter, this); + } else if (currentSectionRect.bottom() < translatedEventRect.bottom()) { + // paint the bottom section + opt.state &= ~QStyle::State_Horizontal; + opt.rect = QRect(0, currentSectionRect.bottom() + 1, + width, height - currentSectionRect.bottom() - 1); + style()->drawControl(QStyle::CE_HeaderEmptyArea, &opt, &painter, this); + } + +#if 0 + // ### visualize section spans + for (int a = 0, i = 0; i < d->sectionSpans.count(); ++i) { + QColor color((i & 4 ? 255 : 0), (i & 2 ? 255 : 0), (i & 1 ? 255 : 0)); + if (d->orientation == Qt::Horizontal) + painter.fillRect(a - d->offset, 0, d->sectionSpans.at(i).size, 4, color); + else + painter.fillRect(0, a - d->offset, 4, d->sectionSpans.at(i).size, color); + a += d->sectionSpans.at(i).size; + } + +#endif +} + +/*! + \reimp +*/ + +void QHeaderView::mousePressEvent(QMouseEvent *e) +{ + Q_D(QHeaderView); + if (d->state != QHeaderViewPrivate::NoState || e->button() != Qt::LeftButton) + return; + int pos = d->orientation == Qt::Horizontal ? e->x() : e->y(); + int handle = d->sectionHandleAt(pos); + d->originalSize = -1; // clear the stored original size + if (handle == -1) { + d->pressed = logicalIndexAt(pos); + if (d->clickableSections) + emit sectionPressed(d->pressed); + if (d->movableSections) { + d->section = d->target = d->pressed; + if (d->section == -1) + return; + d->state = QHeaderViewPrivate::MoveSection; + d->setupSectionIndicator(d->section, pos); + } else if (d->clickableSections && d->pressed != -1) { + updateSection(d->pressed); + d->state = QHeaderViewPrivate::SelectSections; + } + } else if (resizeMode(handle) == Interactive) { + d->originalSize = sectionSize(handle); + d->state = QHeaderViewPrivate::ResizeSection; + d->section = handle; + } + + d->firstPos = pos; + d->lastPos = pos; + + d->clearCascadingSections(); +} + +/*! + \reimp +*/ + +void QHeaderView::mouseMoveEvent(QMouseEvent *e) +{ + Q_D(QHeaderView); + int pos = d->orientation == Qt::Horizontal ? e->x() : e->y(); + if (pos < 0) + return; + if (e->buttons() == Qt::NoButton) { + d->state = QHeaderViewPrivate::NoState; + d->pressed = -1; + } + switch (d->state) { + case QHeaderViewPrivate::ResizeSection: { + Q_ASSERT(d->originalSize != -1); + if (d->cascadingResizing) { + int delta = d->reverse() ? d->lastPos - pos : pos - d->lastPos; + int visual = visualIndex(d->section); + d->cascadingResize(visual, d->headerSectionSize(visual) + delta); + } else { + int delta = d->reverse() ? d->firstPos - pos : pos - d->firstPos; + resizeSection(d->section, qMax(d->originalSize + delta, minimumSectionSize())); + } + d->lastPos = pos; + return; + } + case QHeaderViewPrivate::MoveSection: { + if (qAbs(pos - d->firstPos) >= QApplication::startDragDistance()) { + int indicatorCenter = (d->orientation == Qt::Horizontal + ? d->sectionIndicator->width() + : d->sectionIndicator->height()) / 2; + int centerOffset = indicatorCenter - d->sectionIndicatorOffset; + // This will drop the moved section to the position under the center of the indicator. + // If centerOffset is 0, the section will be moved to the position of the mouse cursor. + int visual = visualIndexAt(pos + centerOffset); + if (visual == -1) + return; + d->target = d->logicalIndex(visual); + d->updateSectionIndicator(d->section, pos); + } else { + int visual = visualIndexAt(d->firstPos); + if (visual == -1) + return; + d->target = d->logicalIndex(visual); + d->updateSectionIndicator(d->section, d->firstPos); + } + return; + } + case QHeaderViewPrivate::SelectSections: { + int logical = logicalIndexAt(pos); + if (logical == d->pressed) + return; // nothing to do + else if (d->pressed != -1) + updateSection(d->pressed); + d->pressed = logical; + if (d->clickableSections && logical != -1) { + emit sectionEntered(d->pressed); + updateSection(d->pressed); + } + return; + } + case QHeaderViewPrivate::NoState: { +#ifndef QT_NO_CURSOR + int handle = d->sectionHandleAt(pos); + bool hasCursor = testAttribute(Qt::WA_SetCursor); + if (handle != -1 && (resizeMode(handle) == Interactive)) { + if (!hasCursor) + setCursor(d->orientation == Qt::Horizontal ? Qt::SplitHCursor : Qt::SplitVCursor); + } else if (hasCursor) { + unsetCursor(); + } +#endif + return; + } + default: + break; + } +} + +/*! + \reimp +*/ + +void QHeaderView::mouseReleaseEvent(QMouseEvent *e) +{ + Q_D(QHeaderView); + int pos = d->orientation == Qt::Horizontal ? e->x() : e->y(); + switch (d->state) { + case QHeaderViewPrivate::MoveSection: + if (!d->sectionIndicator->isHidden()) { // moving + int from = visualIndex(d->section); + Q_ASSERT(from != -1); + int to = visualIndex(d->target); + Q_ASSERT(to != -1); + moveSection(from, to); + d->section = d->target = -1; + d->updateSectionIndicator(d->section, pos); + break; + } // not moving + case QHeaderViewPrivate::SelectSections: + if (!d->clickableSections) { + int section = logicalIndexAt(pos); + updateSection(section); + } + // fall through + case QHeaderViewPrivate::NoState: + if (d->clickableSections) { + int section = logicalIndexAt(pos); + if (section != -1 && section == d->pressed) { + d->flipSortIndicator(section); + emit sectionClicked(logicalIndexAt(pos)); + } + if (d->pressed != -1) + updateSection(d->pressed); + } + break; + case QHeaderViewPrivate::ResizeSection: + d->originalSize = -1; + d->clearCascadingSections(); + break; + default: + break; + } + d->state = QHeaderViewPrivate::NoState; + d->pressed = -1; +} + +/*! + \reimp +*/ + +void QHeaderView::mouseDoubleClickEvent(QMouseEvent *e) +{ + Q_D(QHeaderView); + int pos = d->orientation == Qt::Horizontal ? e->x() : e->y(); + int handle = d->sectionHandleAt(pos); + if (handle > -1 && resizeMode(handle) == Interactive) { + emit sectionHandleDoubleClicked(handle); +#ifndef QT_NO_CURSOR + Qt::CursorShape splitCursor = (d->orientation == Qt::Horizontal) + ? Qt::SplitHCursor : Qt::SplitVCursor; + if (cursor().shape() == splitCursor) { + // signal handlers may have changed the section size + handle = d->sectionHandleAt(pos); + if (!(handle > -1 && resizeMode(handle) == Interactive)) + setCursor(Qt::ArrowCursor); + } +#endif + } else { + emit sectionDoubleClicked(logicalIndexAt(e->pos())); + } +} + +/*! + \reimp +*/ + +bool QHeaderView::viewportEvent(QEvent *e) +{ + Q_D(QHeaderView); + switch (e->type()) { +#ifndef QT_NO_TOOLTIP + case QEvent::ToolTip: { + QHelpEvent *he = static_cast<QHelpEvent*>(e); + int logical = logicalIndexAt(he->pos()); + if (logical != -1) { + QVariant variant = d->model->headerData(logical, d->orientation, Qt::ToolTipRole); + if (variant.isValid()) { + QToolTip::showText(he->globalPos(), variant.toString(), this); + return true; + } + } + break; } +#endif +#ifndef QT_NO_WHATSTHIS + case QEvent::QueryWhatsThis: { + QHelpEvent *he = static_cast<QHelpEvent*>(e); + int logical = logicalIndexAt(he->pos()); + if (logical != -1 + && d->model->headerData(logical, d->orientation, Qt::WhatsThisRole).isValid()) + return true; + break; } + case QEvent::WhatsThis: { + QHelpEvent *he = static_cast<QHelpEvent*>(e); + int logical = logicalIndexAt(he->pos()); + if (logical != -1) { + QVariant whatsthis = d->model->headerData(logical, d->orientation, + Qt::WhatsThisRole); + if (whatsthis.isValid()) { + QWhatsThis::showText(he->globalPos(), whatsthis.toString(), this); + return true; + } + } + break; } +#endif // QT_NO_WHATSTHIS +#ifndef QT_NO_STATUSTIP + case QEvent::StatusTip: { + QHelpEvent *he = static_cast<QHelpEvent*>(e); + int logical = logicalIndexAt(he->pos()); + if (logical != -1) { + QString statustip = d->model->headerData(logical, d->orientation, + Qt::StatusTipRole).toString(); + if (!statustip.isEmpty()) + setStatusTip(statustip); + } + return true; } +#endif // QT_NO_STATUSTIP + case QEvent::Hide: + case QEvent::Show: + case QEvent::FontChange: + case QEvent::StyleChange: + d->invalidateCachedSizeHint(); + resizeSections(); + emit geometriesChanged(); + break; + case QEvent::ContextMenu: { + d->state = QHeaderViewPrivate::NoState; + d->pressed = d->section = d->target = -1; + d->updateSectionIndicator(d->section, -1); + } + default: + break; + } + return QAbstractItemView::viewportEvent(e); +} + +/*! + Paints the section specified by the given \a logicalIndex, using the given + \a painter and \a rect. + + Normally, you do not have to call this function. +*/ + +void QHeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const +{ + Q_D(const QHeaderView); + if (!rect.isValid()) + return; + // get the state of the section + QStyleOptionHeader opt; + initStyleOption(&opt); + QStyle::State state = QStyle::State_None; + if (isEnabled()) + state |= QStyle::State_Enabled; + if (window()->isActiveWindow()) + state |= QStyle::State_Active; + if (d->clickableSections) { + if (logicalIndex == d->hover) + state |= QStyle::State_MouseOver; + if (logicalIndex == d->pressed) + state |= QStyle::State_Sunken; + else if (d->highlightSelected) { + if (d->sectionIntersectsSelection(logicalIndex)) + state |= QStyle::State_On; + if (d->isSectionSelected(logicalIndex)) + state |= QStyle::State_Sunken; + } + + } + if (isSortIndicatorShown() && sortIndicatorSection() == logicalIndex) + opt.sortIndicator = (sortIndicatorOrder() == Qt::AscendingOrder) + ? QStyleOptionHeader::SortDown : QStyleOptionHeader::SortUp; + + // setup the style options structure + QVariant textAlignment = d->model->headerData(logicalIndex, d->orientation, + Qt::TextAlignmentRole); + opt.rect = rect; + opt.section = logicalIndex; + opt.state |= state; + opt.textAlignment = Qt::Alignment(textAlignment.isValid() + ? Qt::Alignment(textAlignment.toInt()) + : d->defaultAlignment); + + opt.iconAlignment = Qt::AlignVCenter; + opt.text = d->model->headerData(logicalIndex, d->orientation, + Qt::DisplayRole).toString(); + if (d->textElideMode != Qt::ElideNone) + opt.text = opt.fontMetrics.elidedText(opt.text, d->textElideMode , rect.width() - 4); + + QVariant variant = d->model->headerData(logicalIndex, d->orientation, + Qt::DecorationRole); + opt.icon = qvariant_cast<QIcon>(variant); + if (opt.icon.isNull()) + opt.icon = qvariant_cast<QPixmap>(variant); + QVariant foregroundBrush = d->model->headerData(logicalIndex, d->orientation, + Qt::ForegroundRole); + if (qVariantCanConvert<QBrush>(foregroundBrush)) + opt.palette.setBrush(QPalette::ButtonText, qvariant_cast<QBrush>(foregroundBrush)); + + QPointF oldBO = painter->brushOrigin(); + QVariant backgroundBrush = d->model->headerData(logicalIndex, d->orientation, + Qt::BackgroundRole); + if (qVariantCanConvert<QBrush>(backgroundBrush)) { + opt.palette.setBrush(QPalette::Button, qvariant_cast<QBrush>(backgroundBrush)); + opt.palette.setBrush(QPalette::Window, qvariant_cast<QBrush>(backgroundBrush)); + painter->setBrushOrigin(opt.rect.topLeft()); + } + + // the section position + int visual = visualIndex(logicalIndex); + Q_ASSERT(visual != -1); + if (count() == 1) + opt.position = QStyleOptionHeader::OnlyOneSection; + else if (visual == 0) + opt.position = QStyleOptionHeader::Beginning; + else if (visual == count() - 1) + opt.position = QStyleOptionHeader::End; + else + opt.position = QStyleOptionHeader::Middle; + opt.orientation = d->orientation; + // the selected position + bool previousSelected = d->isSectionSelected(this->logicalIndex(visual - 1)); + bool nextSelected = d->isSectionSelected(this->logicalIndex(visual + 1)); + if (previousSelected && nextSelected) + opt.selectedPosition = QStyleOptionHeader::NextAndPreviousAreSelected; + else if (previousSelected) + opt.selectedPosition = QStyleOptionHeader::PreviousIsSelected; + else if (nextSelected) + opt.selectedPosition = QStyleOptionHeader::NextIsSelected; + else + opt.selectedPosition = QStyleOptionHeader::NotAdjacent; + // draw the section + style()->drawControl(QStyle::CE_Header, &opt, painter, this); + + painter->setBrushOrigin(oldBO); +} + +/*! + Returns the size of the contents of the section specified by the given + \a logicalIndex. + + \sa defaultSectionSize() +*/ + +QSize QHeaderView::sectionSizeFromContents(int logicalIndex) const +{ + Q_D(const QHeaderView); + Q_ASSERT(logicalIndex >= 0); + + // use SizeHintRole + QVariant variant = d->model->headerData(logicalIndex, d->orientation, Qt::SizeHintRole); + if (variant.isValid()) + return qvariant_cast<QSize>(variant); + + // otherwise use the contents + QStyleOptionHeader opt; + initStyleOption(&opt); + opt.section = logicalIndex; + QVariant var = d->model->headerData(logicalIndex, d->orientation, + Qt::FontRole); + QFont fnt; + if (var.isValid() && qVariantCanConvert<QFont>(var)) + fnt = qvariant_cast<QFont>(var); + else + fnt = font(); + fnt.setBold(true); + opt.fontMetrics = QFontMetrics(fnt); + opt.text = d->model->headerData(logicalIndex, d->orientation, + Qt::DisplayRole).toString(); + variant = d->model->headerData(logicalIndex, d->orientation, Qt::DecorationRole); + opt.icon = qvariant_cast<QIcon>(variant); + if (opt.icon.isNull()) + opt.icon = qvariant_cast<QPixmap>(variant); + QSize size = style()->sizeFromContents(QStyle::CT_HeaderSection, &opt, QSize(), this); + if (isSortIndicatorShown() && sortIndicatorSection() == logicalIndex) { + int margin = style()->pixelMetric(QStyle::PM_HeaderMargin, &opt, this); + if (d->orientation == Qt::Horizontal) + size.rwidth() += size.height() + margin; + else + size.rheight() += size.width() + margin; + } + return size; +} + +/*! + Returns the horizontal offset of the header. This is 0 for vertical + headers. + + \sa offset() +*/ + +int QHeaderView::horizontalOffset() const +{ + Q_D(const QHeaderView); + if (d->orientation == Qt::Horizontal) + return d->offset; + return 0; +} + +/*! + Returns the vertical offset of the header. This is 0 for horizontal + headers. + + \sa offset() +*/ + +int QHeaderView::verticalOffset() const +{ + Q_D(const QHeaderView); + if (d->orientation == Qt::Vertical) + return d->offset; + return 0; +} + +/*! + \reimp + \internal +*/ + +void QHeaderView::updateGeometries() +{ + Q_D(QHeaderView); + d->layoutChildren(); + if (d->hasAutoResizeSections()) + resizeSections(); +} + +/*! + \reimp + \internal +*/ + +void QHeaderView::scrollContentsBy(int dx, int dy) +{ + Q_D(QHeaderView); + d->scrollDirtyRegion(dx, dy); +} + +/*! + \reimp + \internal +*/ +void QHeaderView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + Q_D(QHeaderView); + d->invalidateCachedSizeHint(); + if (d->hasAutoResizeSections()) { + bool resizeRequired = d->globalResizeMode == ResizeToContents; + int first = orientation() == Qt::Horizontal ? topLeft.column() : topLeft.row(); + int last = orientation() == Qt::Horizontal ? bottomRight.column() : bottomRight.row(); + for (int i = first; i <= last && !resizeRequired; ++i) + resizeRequired = (resizeRequired && resizeMode(i)); + if (resizeRequired) + d->doDelayedResizeSections(); + } +} + +/*! + \reimp + \internal + + Empty implementation because the header doesn't show QModelIndex items. +*/ +void QHeaderView::rowsInserted(const QModelIndex &, int, int) +{ + // do nothing +} + +/*! + \reimp + \internal + + Empty implementation because the header doesn't show QModelIndex items. +*/ + +QRect QHeaderView::visualRect(const QModelIndex &) const +{ + return QRect(); +} + +/*! + \reimp + \internal + + Empty implementation because the header doesn't show QModelIndex items. +*/ + +void QHeaderView::scrollTo(const QModelIndex &, ScrollHint) +{ + // do nothing - the header only displays sections +} + +/*! + \reimp + \internal + + Empty implementation because the header doesn't show QModelIndex items. +*/ + +QModelIndex QHeaderView::indexAt(const QPoint &) const +{ + return QModelIndex(); +} + +/*! + \reimp + \internal + + Empty implementation because the header doesn't show QModelIndex items. +*/ + +bool QHeaderView::isIndexHidden(const QModelIndex &) const +{ + return true; // the header view has no items, just sections +} + +/*! + \reimp + \internal + + Empty implementation because the header doesn't show QModelIndex items. +*/ + +QModelIndex QHeaderView::moveCursor(CursorAction, Qt::KeyboardModifiers) +{ + return QModelIndex(); +} + +/*! + \reimp + + Selects the items in the given \a rect according to the specified + \a flags. + + The base class implementation does nothing. +*/ + +void QHeaderView::setSelection(const QRect&, QItemSelectionModel::SelectionFlags) +{ + // do nothing +} + +/*! + \internal +*/ + +QRegion QHeaderView::visualRegionForSelection(const QItemSelection &selection) const +{ + Q_D(const QHeaderView); + const int max = d->modelSectionCount(); + if (d->orientation == Qt::Horizontal) { + int left = max; + int right = 0; + int rangeLeft, rangeRight; + + for (int i = 0; i < selection.count(); ++i) { + QItemSelectionRange r = selection.at(i); + if (r.parent().isValid() || !r.isValid()) + continue; // we only know about toplevel items and we don't want invalid ranges + // FIXME an item inside the range may be the leftmost or rightmost + rangeLeft = visualIndex(r.left()); + if (rangeLeft == -1) // in some cases users may change the selections + continue; // before we have a chance to do the layout + rangeRight = visualIndex(r.right()); + if (rangeRight == -1) // in some cases users may change the selections + continue; // before we have a chance to do the layout + if (rangeLeft < left) + left = rangeLeft; + if (rangeRight > right) + right = rangeRight; + } + + int logicalLeft = logicalIndex(left); + int logicalRight = logicalIndex(right); + + if (logicalLeft < 0 || logicalLeft >= count() || + logicalRight < 0 || logicalRight >= count()) + return QRegion(); + + int leftPos = sectionViewportPosition(logicalLeft); + int rightPos = sectionViewportPosition(logicalRight); + rightPos += sectionSize(logicalRight); + return QRect(leftPos, 0, rightPos - leftPos, height()); + } + // orientation() == Qt::Vertical + int top = max; + int bottom = 0; + int rangeTop, rangeBottom; + + for (int i = 0; i < selection.count(); ++i) { + QItemSelectionRange r = selection.at(i); + if (r.parent().isValid() || !r.isValid()) + continue; // we only know about toplevel items + // FIXME an item inside the range may be the leftmost or rightmost + rangeTop = visualIndex(r.top()); + if (rangeTop == -1) // in some cases users may change the selections + continue; // before we have a chance to do the layout + rangeBottom = visualIndex(r.bottom()); + if (rangeBottom == -1) // in some cases users may change the selections + continue; // before we have a chance to do the layout + if (rangeTop < top) + top = rangeTop; + if (rangeBottom > bottom) + bottom = rangeBottom; + } + + int logicalTop = logicalIndex(top); + int logicalBottom = logicalIndex(bottom); + + if (logicalTop == -1 || logicalBottom == -1) + return QRect(); + + int topPos = sectionViewportPosition(logicalTop); + int bottomPos = sectionViewportPosition(logicalBottom) + sectionSize(logicalBottom); + + return QRect(0, topPos, width(), bottomPos - topPos); +} + + +// private implementation + +int QHeaderViewPrivate::sectionHandleAt(int position) +{ + Q_Q(QHeaderView); + int visual = q->visualIndexAt(position); + if (visual == -1) + return -1; + int log = logicalIndex(visual); + int pos = q->sectionViewportPosition(log); + int grip = q->style()->pixelMetric(QStyle::PM_HeaderGripMargin, 0, q); + + bool atLeft = position < pos + grip; + bool atRight = (position > pos + q->sectionSize(log) - grip); + if (reverse()) + qSwap(atLeft, atRight); + + if (atLeft) { + //grip at the beginning of the section + while(visual > -1) { + int logical = q->logicalIndex(--visual); + if (!q->isSectionHidden(logical)) + return logical; + } + } else if (atRight) { + //grip at the end of the section + return log; + } + return -1; +} + +void QHeaderViewPrivate::setupSectionIndicator(int section, int position) +{ + Q_Q(QHeaderView); + if (!sectionIndicator) { + sectionIndicator = new QLabel(viewport); + } + + int x, y, w, h; + int p = q->sectionViewportPosition(section); + if (orientation == Qt::Horizontal) { + x = p; + y = 0; + w = q->sectionSize(section); + h = viewport->height(); + } else { + x = 0; + y = p; + w = viewport->width(); + h = q->sectionSize(section); + } + sectionIndicator->resize(w, h); + + QPixmap pm(w, h); + pm.fill(QColor(0, 0, 0, 45)); + QRect rect(0, 0, w, h); + + QPainter painter(&pm); + painter.setOpacity(0.75); + q->paintSection(&painter, rect, section); + painter.end(); + + sectionIndicator->setPixmap(pm); + sectionIndicatorOffset = position - qMax(p, 0); +} + +void QHeaderViewPrivate::updateSectionIndicator(int section, int position) +{ + if (!sectionIndicator) + return; + + if (section == -1 || target == -1) { + sectionIndicator->hide(); + return; + } + + if (orientation == Qt::Horizontal) + sectionIndicator->move(position - sectionIndicatorOffset, 0); + else + sectionIndicator->move(0, position - sectionIndicatorOffset); + + sectionIndicator->show(); +} + +/*! + Initialize \a option with the values from this QHeaderView. This method is + useful for subclasses when they need a QStyleOptionHeader, but do not want + to fill in all the information themselves. + + \sa QStyleOption::initFrom() +*/ +void QHeaderView::initStyleOption(QStyleOptionHeader *option) const +{ + Q_D(const QHeaderView); + option->initFrom(this); + option->state = QStyle::State_None | QStyle::State_Raised; + option->orientation = d->orientation; + if (d->orientation == Qt::Horizontal) + option->state |= QStyle::State_Horizontal; + if (isEnabled()) + option->state |= QStyle::State_Enabled; + option->section = 0; +} + +bool QHeaderViewPrivate::isSectionSelected(int section) const +{ + int i = section * 2; + if (i < 0 || i >= sectionSelected.count()) + return false; + if (sectionSelected.testBit(i)) // if the value was cached + return sectionSelected.testBit(i + 1); + bool s = false; + if (orientation == Qt::Horizontal) + s = isColumnSelected(section); + else + s = isRowSelected(section); + sectionSelected.setBit(i + 1, s); // selection state + sectionSelected.setBit(i, true); // cache state + return s; +} + +/*! + \internal + Returns the last visible (ie. not hidden) visual index +*/ +int QHeaderViewPrivate::lastVisibleVisualIndex() const +{ + Q_Q(const QHeaderView); + for (int visual = q->count()-1; visual >= 0; --visual) { + if (!q->isSectionHidden(q->logicalIndex(visual))) + return visual; + } + + //default value if no section is actually visible + return -1; +} + +/*! + \internal + Go through and resize all of the sections applying stretchLastSection, + manualy stretches, sizes, and useGlobalMode. + + The different resize modes are: + Interactive - the user decides the size + Stretch - take up whatever space is left + Fixed - the size is set programmatically outside the header + ResizeToContentes - the size is set based on the contents of the row or column in the parent view + + The resize mode will not affect the last section if stretchLastSection is true. +*/ +void QHeaderViewPrivate::resizeSections(QHeaderView::ResizeMode globalMode, bool useGlobalMode) +{ + Q_Q(QHeaderView); + + executePostedLayout(); + if (sectionCount == 0) + return; + invalidateCachedSizeHint(); + + // find stretchLastSection if we have it + int stretchSection = -1; + if (stretchLastSection && !useGlobalMode) { + for (int i = sectionCount - 1; i >= 0; --i) { + if (!isVisualIndexHidden(i)) { + stretchSection = i; + break; + } + } + } + + // count up the number of strected sections and how much space left for them + int lengthToStrech = (orientation == Qt::Horizontal ? viewport->width() : viewport->height()); + int numberOfStretchedSections = 0; + QList<int> section_sizes; + for (int i = 0; i < sectionCount; ++i) { + if (isVisualIndexHidden(i)) + continue; + + QHeaderView::ResizeMode resizeMode; + if (useGlobalMode && (i != stretchSection)) + resizeMode = globalMode; + else + resizeMode = (i == stretchSection ? QHeaderView::Stretch : visualIndexResizeMode(i)); + + if (resizeMode == QHeaderView::Stretch) { + ++numberOfStretchedSections; + section_sizes.append(headerSectionSize(i)); + continue; + } + + // because it isn't stretch, determine its width and remove that from lengthToStrech + int sectionSize = 0; + if (resizeMode == QHeaderView::Interactive || resizeMode == QHeaderView::Fixed) { + sectionSize = headerSectionSize(i); + } else { // resizeMode == QHeaderView::ResizeToContents + int logicalIndex = q->logicalIndex(i); + sectionSize = qMax(viewSectionSizeHint(logicalIndex), + q->sectionSizeHint(logicalIndex)); + } + section_sizes.append(sectionSize); + lengthToStrech -= sectionSize; + } + + // calculate the new length for all of the stretched sections + int stretchSectionLength = -1; + int pixelReminder = 0; + if (numberOfStretchedSections > 0 && lengthToStrech > 0) { // we have room to stretch in + int hintLengthForEveryStretchedSection = lengthToStrech / numberOfStretchedSections; + stretchSectionLength = qMax(hintLengthForEveryStretchedSection, q->minimumSectionSize()); + pixelReminder = lengthToStrech % numberOfStretchedSections; + } + + int spanStartSection = 0; + int previousSectionLength = 0; + const int lastVisibleSection = lastVisibleVisualIndex(); + + QHeaderView::ResizeMode previousSectionResizeMode = QHeaderView::Interactive; + + // resize each section along the total length + for (int i = 0; i < sectionCount; ++i) { + int oldSectionLength = headerSectionSize(i); + int newSectionLength = -1; + QHeaderView::ResizeMode newSectionResizeMode = headerSectionResizeMode(i); + + if (isVisualIndexHidden(i)) { + newSectionLength = 0; + } else { + QHeaderView::ResizeMode resizeMode; + if (useGlobalMode) + resizeMode = globalMode; + else + resizeMode = (i == stretchSection + ? QHeaderView::Stretch + : visualIndexResizeMode(i)); + if (resizeMode == QHeaderView::Stretch && stretchSectionLength != -1) { + if (i == lastVisibleSection) + newSectionLength = qMax(stretchSectionLength, lastSectionSize); + else + newSectionLength = stretchSectionLength; + if (pixelReminder > 0) { + newSectionLength += 1; + --pixelReminder; + } + section_sizes.removeFirst(); + } else { + newSectionLength = section_sizes.front(); + section_sizes.removeFirst(); + } + } + + //Q_ASSERT(newSectionLength > 0); + if ((previousSectionResizeMode != newSectionResizeMode + || previousSectionLength != newSectionLength) && i > 0) { + int spanLength = (i - spanStartSection) * previousSectionLength; + createSectionSpan(spanStartSection, i - 1, spanLength, previousSectionResizeMode); + //Q_ASSERT(headerLength() == length); + spanStartSection = i; + } + + if (newSectionLength != oldSectionLength) + emit q->sectionResized(logicalIndex(i), oldSectionLength, newSectionLength); + + previousSectionLength = newSectionLength; + previousSectionResizeMode = newSectionResizeMode; + } + + createSectionSpan(spanStartSection, sectionCount - 1, + (sectionCount - spanStartSection) * previousSectionLength, + previousSectionResizeMode); + //Q_ASSERT(headerLength() == length); + + viewport->update(); +} + +void QHeaderViewPrivate::createSectionSpan(int start, int end, int size, QHeaderView::ResizeMode mode) +{ + // ### the code for merging spans does not merge at all opertuneties + // ### what if the number of sections is reduced ? + + SectionSpan span(size, (end - start) + 1, mode); + int start_section = 0; +#ifndef QT_NO_DEBUG + int initial_section_count = headerSectionCount(); // ### debug code +#endif + + QList<int> spansToRemove; + for (int i = 0; i < sectionSpans.count(); ++i) { + int end_section = start_section + sectionSpans.at(i).count - 1; + int section_count = sectionSpans.at(i).count; + if (start <= start_section && end > end_section) { + // the existing span is entirely coveded by the new span + spansToRemove.append(i); + } else if (start < start_section && end >= end_section) { + // the existing span is entirely coveded by the new span + spansToRemove.append(i); + } else if (start == start_section && end == end_section) { + // the new span is covered by an existin span + length -= sectionSpans.at(i).size; + length += size; + sectionSpans[i].size = size; + sectionSpans[i].resizeMode = mode; + // ### check if we can merge the section with any of its neighbours + removeSpans(spansToRemove); + Q_ASSERT(initial_section_count == headerSectionCount()); + return; + } else if (start > start_section && end < end_section) { + if (sectionSpans.at(i).sectionSize() == span.sectionSize() + && sectionSpans.at(i).resizeMode == span.resizeMode) { + Q_ASSERT(initial_section_count == headerSectionCount()); + return; + } + // the new span is in the middle of the old span, so we have to split it + length -= sectionSpans.at(i).size; + int section_size = sectionSpans.at(i).sectionSize(); +#ifndef QT_NO_DEBUG + int span_count = sectionSpans.at(i).count; +#endif + QHeaderView::ResizeMode span_mode = sectionSpans.at(i).resizeMode; + // first span + int first_span_count = start - start_section; + int first_span_size = section_size * first_span_count; + sectionSpans[i].count = first_span_count; + sectionSpans[i].size = first_span_size; + sectionSpans[i].resizeMode = span_mode; + length += first_span_size; + // middle span (the new span) +#ifndef QT_NO_DEBUG + int mid_span_count = span.count; +#endif + int mid_span_size = span.size; + sectionSpans.insert(i + 1, span); + length += mid_span_size; + // last span + int last_span_count = end_section - end; + int last_span_size = section_size * last_span_count; + sectionSpans.insert(i + 2, SectionSpan(last_span_size, last_span_count, span_mode)); + length += last_span_size; + Q_ASSERT(span_count == first_span_count + mid_span_count + last_span_count); + removeSpans(spansToRemove); + Q_ASSERT(initial_section_count == headerSectionCount()); + return; + } else if (start > start_section && start <= end_section && end >= end_section) { + // the new span covers the last part of the existing span + length -= sectionSpans.at(i).size; + int removed_count = (end_section - start + 1); + int span_count = sectionSpans.at(i).count - removed_count; + int section_size = sectionSpans.at(i).sectionSize(); + int span_size = section_size * span_count; + sectionSpans[i].count = span_count; + sectionSpans[i].size = span_size; + length += span_size; + if (end == end_section) { + sectionSpans.insert(i + 1, span); // insert after + length += span.size; + removeSpans(spansToRemove); + Q_ASSERT(initial_section_count == headerSectionCount()); + return; + } + } else if (end < end_section && end >= start_section && start <= start_section) { + // the new span covers the first part of the existing span + length -= sectionSpans.at(i).size; + int removed_count = (end - start_section + 1); + int section_size = sectionSpans.at(i).sectionSize(); + int span_count = sectionSpans.at(i).count - removed_count; + int span_size = section_size * span_count; + sectionSpans[i].count = span_count; + sectionSpans[i].size = span_size; + length += span_size; + sectionSpans.insert(i, span); // insert before + length += span.size; + removeSpans(spansToRemove); + Q_ASSERT(initial_section_count == headerSectionCount()); + return; + } + start_section += section_count; + } + + // ### adding and removing _ sections_ in addition to spans + // ### add some more checks here + + if (spansToRemove.isEmpty()) { + if (!sectionSpans.isEmpty() + && sectionSpans.last().sectionSize() == span.sectionSize() + && sectionSpans.last().resizeMode == span.resizeMode) { + length += span.size; + int last = sectionSpans.count() - 1; + sectionSpans[last].count += span.count; + sectionSpans[last].size += span.size; + sectionSpans[last].resizeMode = span.resizeMode; + } else { + length += span.size; + sectionSpans.append(span); + } + } else { + removeSpans(spansToRemove); + length += span.size; + sectionSpans.insert(spansToRemove.first(), span); + //Q_ASSERT(initial_section_count == headerSectionCount()); + } +} + +void QHeaderViewPrivate::removeSectionsFromSpans(int start, int end) +{ + // remove sections + int start_section = 0; + QList<int> spansToRemove; + for (int i = 0; i < sectionSpans.count(); ++i) { + int end_section = start_section + sectionSpans.at(i).count - 1; + int section_size = sectionSpans.at(i).sectionSize(); + int section_count = sectionSpans.at(i).count; + if (start <= start_section && end >= end_section) { + // the change covers the entire span + spansToRemove.append(i); + if (end == end_section) + break; + } else if (start > start_section && end < end_section) { + // all the removed sections are inside the span + int change = (end - start + 1); + sectionSpans[i].count -= change; + sectionSpans[i].size = section_size * sectionSpans.at(i).count; + length -= (change * section_size); + break; + } else if (start >= start_section && start <= end_section) { + // the some of the removed sections are inside the span,at the end + int change = qMin(end_section - start + 1, end - start + 1); + sectionSpans[i].count -= change; + sectionSpans[i].size = section_size * sectionSpans.at(i).count; + start += change; + length -= (change * section_size); + // the change affects several spans + } else if (end >= start_section && end <= end_section) { + // the some of the removed sections are inside the span, at the beginning + int change = qMin((end - start_section + 1), end - start + 1); + sectionSpans[i].count -= change; + sectionSpans[i].size = section_size * sectionSpans.at(i).count; + length -= (change * section_size); + break; + } + start_section += section_count; + } + + for (int i = spansToRemove.count() - 1; i >= 0; --i) { + int s = spansToRemove.at(i); + length -= sectionSpans.at(s).size; + sectionSpans.remove(s); + // ### merge remaining spans + } +} + +void QHeaderViewPrivate::clear() +{ + if (state != NoClear) { + length = 0; + sectionCount = 0; + visualIndices.clear(); + logicalIndices.clear(); + sectionSelected.clear(); + sectionHidden.clear(); + hiddenSectionSize.clear(); + sectionSpans.clear(); + } +} + +void QHeaderViewPrivate::flipSortIndicator(int section) +{ + Q_Q(QHeaderView); + bool ascending = (sortIndicatorSection != section + || sortIndicatorOrder == Qt::DescendingOrder); + q->setSortIndicator(section, ascending ? Qt::AscendingOrder : Qt::DescendingOrder); +} + +void QHeaderViewPrivate::cascadingResize(int visual, int newSize) +{ + Q_Q(QHeaderView); + const int minimumSize = q->minimumSectionSize(); + const int oldSize = headerSectionSize(visual); + int delta = newSize - oldSize; + + if (delta > 0) { // larger + bool sectionResized = false; + + // restore old section sizes + for (int i = firstCascadingSection; i < visual; ++i) { + if (cascadingSectionSize.contains(i)) { + int currentSectionSize = headerSectionSize(i); + int originalSectionSize = cascadingSectionSize.value(i); + if (currentSectionSize < originalSectionSize) { + int newSectionSize = currentSectionSize + delta; + resizeSectionSpan(i, currentSectionSize, newSectionSize); + if (newSectionSize >= originalSectionSize && false) + cascadingSectionSize.remove(i); // the section is now restored + sectionResized = true; + break; + } + } + + } + + // resize the section + if (!sectionResized) { + newSize = qMax(newSize, minimumSize); + if (oldSize != newSize) + resizeSectionSpan(visual, oldSize, newSize); + } + + // cascade the section size change + for (int i = visual + 1; i < sectionCount; ++i) { + if (!sectionIsCascadable(i)) + continue; + int currentSectionSize = headerSectionSize(i); + if (currentSectionSize <= minimumSize) + continue; + int newSectionSize = qMax(currentSectionSize - delta, minimumSize); + //qDebug() << "### cascading to" << i << newSectionSize - currentSectionSize << delta; + resizeSectionSpan(i, currentSectionSize, newSectionSize); + saveCascadingSectionSize(i, currentSectionSize); + delta = delta - (currentSectionSize - newSectionSize); + //qDebug() << "new delta" << delta; + //if (newSectionSize != minimumSize) + if (delta <= 0) + break; + } + } else { // smaller + bool sectionResized = false; + + // restore old section sizes + for (int i = lastCascadingSection; i > visual; --i) { + if (!cascadingSectionSize.contains(i)) + continue; + int currentSectionSize = headerSectionSize(i); + int originalSectionSize = cascadingSectionSize.value(i); + if (currentSectionSize >= originalSectionSize) + continue; + int newSectionSize = currentSectionSize - delta; + resizeSectionSpan(i, currentSectionSize, newSectionSize); + if (newSectionSize >= originalSectionSize && false) { + //qDebug() << "section" << i << "restored to" << originalSectionSize; + cascadingSectionSize.remove(i); // the section is now restored + } + sectionResized = true; + break; + } + + // resize the section + resizeSectionSpan(visual, oldSize, qMax(newSize, minimumSize)); + + // cascade the section size change + if (delta < 0 && newSize < minimumSize) { + for (int i = visual - 1; i >= 0; --i) { + if (!sectionIsCascadable(i)) + continue; + int sectionSize = headerSectionSize(i); + if (sectionSize <= minimumSize) + continue; + resizeSectionSpan(i, sectionSize, qMax(sectionSize + delta, minimumSize)); + saveCascadingSectionSize(i, sectionSize); + break; + } + } + + // let the next section get the space from the resized section + if (!sectionResized) { + for (int i = visual + 1; i < sectionCount; ++i) { + if (!sectionIsCascadable(i)) + continue; + int currentSectionSize = headerSectionSize(i); + int newSectionSize = qMax(currentSectionSize - delta, minimumSize); + resizeSectionSpan(i, currentSectionSize, newSectionSize); + break; + } + } + } + + if (hasAutoResizeSections()) + doDelayedResizeSections(); + + viewport->update(); +} + +void QHeaderViewPrivate::resizeSectionSpan(int visualIndex, int oldSize, int newSize) +{ + Q_Q(QHeaderView); + QHeaderView::ResizeMode mode = headerSectionResizeMode(visualIndex); + createSectionSpan(visualIndex, visualIndex, newSize, mode); + emit q->sectionResized(logicalIndex(visualIndex), oldSize, newSize); +} + +int QHeaderViewPrivate::headerSectionSize(int visual) const +{ + // ### stupid iteration + int section_start = 0; + const int sectionSpansCount = sectionSpans.count(); + for (int i = 0; i < sectionSpansCount; ++i) { + const QHeaderViewPrivate::SectionSpan ¤tSection = sectionSpans.at(i); + int section_end = section_start + currentSection.count - 1; + if (visual >= section_start && visual <= section_end) + return currentSection.sectionSize(); + section_start = section_end + 1; + } + return -1; +} + +int QHeaderViewPrivate::headerSectionPosition(int visual) const +{ + // ### stupid iteration + int section_start = 0; + int span_position = 0; + const int sectionSpansCount = sectionSpans.count(); + for (int i = 0; i < sectionSpansCount; ++i) { + const QHeaderViewPrivate::SectionSpan ¤tSection = sectionSpans.at(i); + int section_end = section_start + currentSection.count - 1; + if (visual >= section_start && visual <= section_end) + return span_position + (visual - section_start) * currentSection.sectionSize(); + section_start = section_end + 1; + span_position += currentSection.size; + } + return -1; +} + +int QHeaderViewPrivate::headerVisualIndexAt(int position) const +{ + // ### stupid iteration + int span_start_section = 0; + int span_position = 0; + const int sectionSpansCount = sectionSpans.count(); + for (int i = 0; i < sectionSpansCount; ++i) { + const QHeaderViewPrivate::SectionSpan ¤tSection = sectionSpans.at(i); + int next_span_start_section = span_start_section + currentSection.count; + int next_span_position = span_position + currentSection.size; + if (position == span_position) + return span_start_section; // spans with no size + if (position > span_position && position < next_span_position) { + int position_in_span = position - span_position; + return span_start_section + (position_in_span / currentSection.sectionSize()); + } + span_start_section = next_span_start_section; + span_position = next_span_position; + } + return -1; +} + +void QHeaderViewPrivate::setHeaderSectionResizeMode(int visual, QHeaderView::ResizeMode mode) +{ + int size = headerSectionSize(visual); + createSectionSpan(visual, visual, size, mode); +} + +QHeaderView::ResizeMode QHeaderViewPrivate::headerSectionResizeMode(int visual) const +{ + int span = sectionSpanIndex(visual); + if (span == -1) + return globalResizeMode; + return sectionSpans.at(span).resizeMode; +} + +void QHeaderViewPrivate::setGlobalHeaderResizeMode(QHeaderView::ResizeMode mode) +{ + globalResizeMode = mode; + for (int i = 0; i < sectionSpans.count(); ++i) + sectionSpans[i].resizeMode = mode; +} + +int QHeaderViewPrivate::viewSectionSizeHint(int logical) const +{ + Q_Q(const QHeaderView); + if (QAbstractItemView *parent = qobject_cast<QAbstractItemView*>(q->parent())) { + return (orientation == Qt::Horizontal + ? parent->sizeHintForColumn(logical) + : parent->sizeHintForRow(logical)); + } + return 0; +} + +int QHeaderViewPrivate::adjustedVisualIndex(int visualIndex) const +{ + if (hiddenSectionSize.count() > 0) { + int adjustedVisualIndex = visualIndex; + int currentVisualIndex = 0; + for (int i = 0; i < sectionSpans.count(); ++i) { + if (sectionSpans.at(i).size == 0) + adjustedVisualIndex += sectionSpans.at(i).count; + else + currentVisualIndex += sectionSpans.at(i).count; + if (currentVisualIndex >= visualIndex) + break; + } + visualIndex = adjustedVisualIndex; + } + return visualIndex; +} + +#ifndef QT_NO_DATASTREAM +void QHeaderViewPrivate::write(QDataStream &out) const +{ + out << int(orientation); + out << int(sortIndicatorOrder); + out << sortIndicatorSection; + out << sortIndicatorShown; + + out << visualIndices; + out << logicalIndices; + + out << sectionHidden; + out << hiddenSectionSize; + + out << length; + out << sectionCount; + out << movableSections; + out << clickableSections; + out << highlightSelected; + out << stretchLastSection; + out << cascadingResizing; + out << stretchSections; + out << contentsSections; + out << defaultSectionSize; + out << minimumSectionSize; + + out << int(defaultAlignment); + out << int(globalResizeMode); + + out << sectionSpans; +} + +bool QHeaderViewPrivate::read(QDataStream &in) +{ + int orient, order, align, global; + in >> orient; + orientation = (Qt::Orientation)orient; + + in >> order; + sortIndicatorOrder = (Qt::SortOrder)order; + + in >> sortIndicatorSection; + in >> sortIndicatorShown; + + in >> visualIndices; + in >> logicalIndices; + + in >> sectionHidden; + in >> hiddenSectionSize; + + in >> length; + in >> sectionCount; + in >> movableSections; + in >> clickableSections; + in >> highlightSelected; + in >> stretchLastSection; + in >> cascadingResizing; + in >> stretchSections; + in >> contentsSections; + in >> defaultSectionSize; + in >> minimumSectionSize; + + in >> align; + defaultAlignment = (Qt::Alignment)align; + + in >> global; + globalResizeMode = (QHeaderView::ResizeMode)global; + + in >> sectionSpans; + + return true; +} + +QT_END_NAMESPACE + +#endif // QT_NO_DATASTREAEM + +#endif // QT_NO_ITEMVIEWS + +#include "moc_qheaderview.cpp" diff --git a/src/gui/itemviews/qheaderview.h b/src/gui/itemviews/qheaderview.h new file mode 100644 index 0000000..47f5269 --- /dev/null +++ b/src/gui/itemviews/qheaderview.h @@ -0,0 +1,251 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QHEADERVIEW_H +#define QHEADERVIEW_H + +#include <QtGui/qabstractitemview.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_ITEMVIEWS + +class QHeaderViewPrivate; +class QStyleOptionHeader; + +class Q_GUI_EXPORT QHeaderView : public QAbstractItemView +{ + Q_OBJECT + Q_PROPERTY(bool showSortIndicator READ isSortIndicatorShown WRITE setSortIndicatorShown) + Q_PROPERTY(bool highlightSections READ highlightSections WRITE setHighlightSections) + Q_PROPERTY(bool stretchLastSection READ stretchLastSection WRITE setStretchLastSection) + Q_PROPERTY(bool cascadingSectionResizes READ cascadingSectionResizes WRITE setCascadingSectionResizes) + Q_PROPERTY(int defaultSectionSize READ defaultSectionSize WRITE setDefaultSectionSize) + Q_PROPERTY(int minimumSectionSize READ minimumSectionSize WRITE setMinimumSectionSize) + Q_PROPERTY(Qt::Alignment defaultAlignment READ defaultAlignment WRITE setDefaultAlignment) + Q_ENUMS(ResizeMode) + +public: + + enum ResizeMode + { + Interactive, + Stretch, + Fixed, + ResizeToContents, + Custom = Fixed + }; + + explicit QHeaderView(Qt::Orientation orientation, QWidget *parent = 0); + virtual ~QHeaderView(); + + void setModel(QAbstractItemModel *model); + + Qt::Orientation orientation() const; + int offset() const; + int length() const; + QSize sizeHint() const; + int sectionSizeHint(int logicalIndex) const; + + int visualIndexAt(int position) const; + int logicalIndexAt(int position) const; + + inline int logicalIndexAt(int x, int y) const; + inline int logicalIndexAt(const QPoint &pos) const; + + int sectionSize(int logicalIndex) const; + int sectionPosition(int logicalIndex) const; + int sectionViewportPosition(int logicalIndex) const; + + void moveSection(int from, int to); + void swapSections(int first, int second); + void resizeSection(int logicalIndex, int size); + void resizeSections(QHeaderView::ResizeMode mode); + + bool isSectionHidden(int logicalIndex) const; + void setSectionHidden(int logicalIndex, bool hide); + int hiddenSectionCount() const; + + inline void hideSection(int logicalIndex); + inline void showSection(int logicalIndex); + + int count() const; + int visualIndex(int logicalIndex) const; + int logicalIndex(int visualIndex) const; + + void setMovable(bool movable); + bool isMovable() const; + + void setClickable(bool clickable); + bool isClickable() const; + + void setHighlightSections(bool highlight); + bool highlightSections() const; + + void setResizeMode(ResizeMode mode); + void setResizeMode(int logicalIndex, ResizeMode mode); + ResizeMode resizeMode(int logicalIndex) const; + int stretchSectionCount() const; + + void setSortIndicatorShown(bool show); + bool isSortIndicatorShown() const; + + void setSortIndicator(int logicalIndex, Qt::SortOrder order); + int sortIndicatorSection() const; + Qt::SortOrder sortIndicatorOrder() const; + + bool stretchLastSection() const; + void setStretchLastSection(bool stretch); + + bool cascadingSectionResizes() const; + void setCascadingSectionResizes(bool enable); + + int defaultSectionSize() const; + void setDefaultSectionSize(int size); + + int minimumSectionSize() const; + void setMinimumSectionSize(int size); + + Qt::Alignment defaultAlignment() const; + void setDefaultAlignment(Qt::Alignment alignment); + + void doItemsLayout(); + bool sectionsMoved() const; + bool sectionsHidden() const; + +#ifndef QT_NO_DATASTREAM + QByteArray saveState() const; + bool restoreState(const QByteArray &state); +#endif + + void reset(); + +public Q_SLOTS: + void setOffset(int offset); + void setOffsetToSectionPosition(int visualIndex); + void setOffsetToLastSection(); + void headerDataChanged(Qt::Orientation orientation, int logicalFirst, int logicalLast); + +Q_SIGNALS: + void sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex); + void sectionResized(int logicalIndex, int oldSize, int newSize); + void sectionPressed(int logicalIndex); + void sectionClicked(int logicalIndex); + void sectionEntered(int logicalIndex); + void sectionDoubleClicked(int logicalIndex); + void sectionCountChanged(int oldCount, int newCount); + void sectionHandleDoubleClicked(int logicalIndex); + void sectionAutoResize(int logicalIndex, QHeaderView::ResizeMode mode); + void geometriesChanged(); + void sortIndicatorChanged(int logicalIndex, Qt::SortOrder order); + +protected Q_SLOTS: + void updateSection(int logicalIndex); + void resizeSections(); + void sectionsInserted(const QModelIndex &parent, int logicalFirst, int logicalLast); + void sectionsAboutToBeRemoved(const QModelIndex &parent, int logicalFirst, int logicalLast); + +protected: + QHeaderView(QHeaderViewPrivate &dd, Qt::Orientation orientation, QWidget *parent = 0); + void initialize(); + + void initializeSections(); + void initializeSections(int start, int end); + void currentChanged(const QModelIndex ¤t, const QModelIndex &old); + + bool event(QEvent *e); + void paintEvent(QPaintEvent *e); + void mousePressEvent(QMouseEvent *e); + void mouseMoveEvent(QMouseEvent *e); + void mouseReleaseEvent(QMouseEvent *e); + void mouseDoubleClickEvent(QMouseEvent *e); + bool viewportEvent(QEvent *e); + + virtual void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const; + virtual QSize sectionSizeFromContents(int logicalIndex) const; + + int horizontalOffset() const; + int verticalOffset() const; + void updateGeometries(); + void scrollContentsBy(int dx, int dy); + + void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void rowsInserted(const QModelIndex &parent, int start, int end); + + QRect visualRect(const QModelIndex &index) const; + void scrollTo(const QModelIndex &index, ScrollHint hint); + + QModelIndex indexAt(const QPoint &p) const; + bool isIndexHidden(const QModelIndex &index) const; + + QModelIndex moveCursor(CursorAction, Qt::KeyboardModifiers); + void setSelection(const QRect&, QItemSelectionModel::SelectionFlags); + QRegion visualRegionForSelection(const QItemSelection &selection) const; + void initStyleOption(QStyleOptionHeader *option) const; + +private: + Q_PRIVATE_SLOT(d_func(), void _q_sectionsRemoved(const QModelIndex &parent, int logicalFirst, int logicalLast)) + Q_PRIVATE_SLOT(d_func(), void _q_layoutAboutToBeChanged()) + Q_PRIVATE_SLOT(d_func(), void _q_layoutChanged()) + Q_DECLARE_PRIVATE(QHeaderView) + Q_DISABLE_COPY(QHeaderView) +}; + +inline int QHeaderView::logicalIndexAt(int ax, int ay) const +{ return orientation() == Qt::Horizontal ? logicalIndexAt(ax) : logicalIndexAt(ay); } +inline int QHeaderView::logicalIndexAt(const QPoint &apos) const +{ return logicalIndexAt(apos.x(), apos.y()); } +inline void QHeaderView::hideSection(int alogicalIndex) +{ setSectionHidden(alogicalIndex, true); } +inline void QHeaderView::showSection(int alogicalIndex) +{ setSectionHidden(alogicalIndex, false); } + +#endif // QT_NO_ITEMVIEWS + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QHEADERVIEW_H diff --git a/src/gui/itemviews/qheaderview_p.h b/src/gui/itemviews/qheaderview_p.h new file mode 100644 index 0000000..fbba69a --- /dev/null +++ b/src/gui/itemviews/qheaderview_p.h @@ -0,0 +1,370 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QHEADERVIEW_P_H +#define QHEADERVIEW_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qabstractitemview_p.h" + +#ifndef QT_NO_ITEMVIEWS + +#include "QtCore/qbitarray.h" +#include "QtGui/qapplication.h" +#include "QtGui/qlabel.h" + +QT_BEGIN_NAMESPACE + +class QHeaderViewPrivate: public QAbstractItemViewPrivate +{ + Q_DECLARE_PUBLIC(QHeaderView) + +public: + enum StateVersion { VersionMarker = 0xff }; + + QHeaderViewPrivate() + : state(NoState), + offset(0), + sortIndicatorOrder(Qt::DescendingOrder), + sortIndicatorSection(0), + sortIndicatorShown(false), + lastPos(-1), + firstPos(-1), + originalSize(-1), + section(-1), + target(-1), + pressed(-1), + hover(-1), + length(0), + sectionCount(0), + movableSections(false), + clickableSections(false), + highlightSelected(false), + stretchLastSection(false), + cascadingResizing(false), + forceInitializing(false), + stretchSections(0), + contentsSections(0), + minimumSectionSize(-1), + lastSectionSize(0), + sectionIndicatorOffset(0), + sectionIndicator(0), + globalResizeMode(QHeaderView::Interactive) + {} + + + int lastVisibleVisualIndex() const; + int sectionHandleAt(int position); + void setupSectionIndicator(int section, int position); + void updateSectionIndicator(int section, int position); + void updateHiddenSections(int logicalFirst, int logicalLast); + void resizeSections(QHeaderView::ResizeMode globalMode, bool useGlobalMode = false); + void _q_sectionsRemoved(const QModelIndex &,int,int); + void _q_layoutAboutToBeChanged(); + void _q_layoutChanged(); + + bool isSectionSelected(int section) const; + + inline bool rowIntersectsSelection(int row) const { + return (selectionModel ? selectionModel->rowIntersectsSelection(row, root) : false); + } + + inline bool columnIntersectsSelection(int column) const { + return (selectionModel ? selectionModel->columnIntersectsSelection(column, root) : false); + } + + inline bool sectionIntersectsSelection(int logical) const { + return (orientation == Qt::Horizontal ? columnIntersectsSelection(logical) : rowIntersectsSelection(logical)); + } + + inline bool isRowSelected(int row) const { + return (selectionModel ? selectionModel->isRowSelected(row, root) : false); + } + + inline bool isColumnSelected(int column) const { + return (selectionModel ? selectionModel->isColumnSelected(column, root) : false); + } + + inline void prepareSectionSelected() { + if (!selectionModel || !selectionModel->hasSelection()) + sectionSelected.clear(); + else if (sectionSelected.count() != sectionCount * 2) + sectionSelected.fill(false, sectionCount * 2); + else sectionSelected.fill(false); + } + + inline bool reverse() const { + return orientation == Qt::Horizontal && q_func()->isRightToLeft(); + } + + inline int logicalIndex(int visualIndex) const { + return logicalIndices.isEmpty() ? visualIndex : logicalIndices.at(visualIndex); + } + + inline int visualIndex(int logicalIndex) const { + return visualIndices.isEmpty() ? logicalIndex : visualIndices.at(logicalIndex); + } + + inline void setDefaultValues(Qt::Orientation o) { + orientation = o; + defaultSectionSize = (o == Qt::Horizontal ? 100 + : qMax(q_func()->minimumSectionSize(), 30)); + defaultAlignment = (o == Qt::Horizontal + ? Qt::Alignment(Qt::AlignCenter) + : Qt::AlignLeft|Qt::AlignVCenter); + } + + inline bool isVisualIndexHidden(int visual) const { + return !sectionHidden.isEmpty() && sectionHidden.at(visual); + } + + inline void setVisualIndexHidden(int visual, bool hidden) { + if (!sectionHidden.isEmpty()) sectionHidden.setBit(visual, hidden); + } + + inline QHeaderView::ResizeMode visualIndexResizeMode(int visual) const { + return headerSectionResizeMode(visual); + } + + inline bool hasAutoResizeSections() const { + return stretchSections || stretchLastSection || contentsSections; + } + + QStyleOptionHeader getStyleOption() const; + + inline void invalidateCachedSizeHint() const { + cachedSizeHint = QSize(); + } + + inline void initializeIndexMapping() const { + if (visualIndices.count() != sectionCount + || logicalIndices.count() != sectionCount) { + visualIndices.resize(sectionCount); + logicalIndices.resize(sectionCount); + for (int s = 0; s < sectionCount; ++s) { + visualIndices[s] = s; + logicalIndices[s] = s; + } + } + } + + inline void clearCascadingSections() { + firstCascadingSection = sectionCount; + lastCascadingSection = 0; + cascadingSectionSize.clear(); + } + + inline void saveCascadingSectionSize(int visual, int size) { + if (!cascadingSectionSize.contains(visual)) { + cascadingSectionSize.insert(visual, size); + firstCascadingSection = qMin(firstCascadingSection, visual); + lastCascadingSection = qMax(lastCascadingSection, visual); + } + } + + inline bool sectionIsCascadable(int visual) const { + return visualIndexResizeMode(visual) == QHeaderView::Interactive; + } + + inline int modelSectionCount() const { + return (orientation == Qt::Horizontal + ? model->columnCount(root) + : model->rowCount(root)); + } + + inline bool modelIsEmpty() const { + return (model->rowCount(root) == 0 || model->columnCount(root) == 0); + } + + inline void doDelayedResizeSections() { + if (!delayedResize.isActive()) + delayedResize.start(0, q_func()); + } + + inline void executePostedResize() const { + if (delayedResize.isActive() && state == NoState) { + delayedResize.stop(); + const_cast<QHeaderView*>(q_func())->resizeSections(); + } + } + + void clear(); + void flipSortIndicator(int section); + void cascadingResize(int visual, int newSize); + + enum State { NoState, ResizeSection, MoveSection, SelectSections, NoClear } state; + + int offset; + Qt::Orientation orientation; + Qt::SortOrder sortIndicatorOrder; + int sortIndicatorSection; + bool sortIndicatorShown; + + mutable QVector<int> visualIndices; // visualIndex = visualIndices.at(logicalIndex) + mutable QVector<int> logicalIndices; // logicalIndex = row or column in the model + mutable QBitArray sectionSelected; // from logical index to bit + mutable QBitArray sectionHidden; // from visual index to bit + mutable QHash<int, int> hiddenSectionSize; // from logical index to section size + mutable QHash<int, int> cascadingSectionSize; // from visual index to section size + mutable QSize cachedSizeHint; + mutable QBasicTimer delayedResize; + + int firstCascadingSection; + int lastCascadingSection; + + int lastPos; + int firstPos; + int originalSize; + int section; // used for resizing and moving sections + int target; + int pressed; + int hover; + + int length; + int sectionCount; + bool movableSections; + bool clickableSections; + bool highlightSelected; + bool stretchLastSection; + bool cascadingResizing; + bool forceInitializing; + int stretchSections; + int contentsSections; + int defaultSectionSize; + int minimumSectionSize; + int lastSectionSize; // $$$ + int sectionIndicatorOffset; + Qt::Alignment defaultAlignment; + QLabel *sectionIndicator; + QHeaderView::ResizeMode globalResizeMode; + QList<QPersistentModelIndex> persistentHiddenSections; + + // header section spans + + struct SectionSpan { + int size; + int count; + QHeaderView::ResizeMode resizeMode; + inline SectionSpan() : size(0), count(0), resizeMode(QHeaderView::Interactive) {} + inline SectionSpan(int length, int sections, QHeaderView::ResizeMode mode) + : size(length), count(sections), resizeMode(mode) {} + inline int sectionSize() const { return (count > 0 ? size / count : 0); } +#ifndef QT_NO_DATASTREAM + inline void write(QDataStream &out) const + { out << size; out << count; out << (int)resizeMode; } + inline void read(QDataStream &in) + { in >> size; in >> count; int m; in >> m; resizeMode = (QHeaderView::ResizeMode)m; } +#endif + }; + + QVector<SectionSpan> sectionSpans; + + void createSectionSpan(int start, int end, int size, QHeaderView::ResizeMode mode); + void removeSectionsFromSpans(int start, int end); + void resizeSectionSpan(int visualIndex, int oldSize, int newSize); + + inline int headerSectionCount() const { // for debugging + int count = 0; + for (int i = 0; i < sectionSpans.count(); ++i) + count += sectionSpans.at(i).count; + return count; + } + + inline int headerLength() const { // for debugging + int len = 0; + for (int i = 0; i < sectionSpans.count(); ++i) + len += sectionSpans.at(i).size; + return len; + } + + inline void removeSpans(const QList<int> &spans) { + for (int i = spans.count() - 1; i >= 0; --i) { + length -= sectionSpans.at(spans.at(i)).size; + sectionSpans.remove(spans.at(i)); + } + } + + inline int sectionSpanIndex(int visual) const { + int section_start = 0; + for (int i = 0; i < sectionSpans.count(); ++i) { + int section_end = section_start + sectionSpans.at(i).count - 1; + if (visual >= section_start && visual <= section_end) + return i; + section_start = section_end + 1; + } + return -1; + } + + int headerSectionSize(int visual) const; + int headerSectionPosition(int visual) const; + int headerVisualIndexAt(int position) const; + + // resize mode + void setHeaderSectionResizeMode(int visual, QHeaderView::ResizeMode mode); + QHeaderView::ResizeMode headerSectionResizeMode(int visual) const; + void setGlobalHeaderResizeMode(QHeaderView::ResizeMode mode); + + // other + int viewSectionSizeHint(int logical) const; + int adjustedVisualIndex(int visualIndex) const; + +#ifndef QT_NO_DATASTREAM + void write(QDataStream &out) const; + bool read(QDataStream &in); +#endif + +}; + +QT_END_NAMESPACE + +#endif // QT_NO_ITEMVIEWS + +#endif // QHEADERVIEW_P_H diff --git a/src/gui/itemviews/qitemdelegate.cpp b/src/gui/itemviews/qitemdelegate.cpp new file mode 100644 index 0000000..0aad6bb --- /dev/null +++ b/src/gui/itemviews/qitemdelegate.cpp @@ -0,0 +1,1325 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qitemdelegate.h" + +#ifndef QT_NO_ITEMVIEWS +#include <qabstractitemmodel.h> +#include <qapplication.h> +#include <qbrush.h> +#include <qlineedit.h> +#include <qtextedit.h> +#include <qpainter.h> +#include <qpalette.h> +#include <qpoint.h> +#include <qrect.h> +#include <qsize.h> +#include <qstyle.h> +#include <qdatetime.h> +#include <qstyleoption.h> +#include <qevent.h> +#include <qpixmap.h> +#include <qbitmap.h> +#include <qpixmapcache.h> +#include <qitemeditorfactory.h> +#include <qmetaobject.h> +#include <qtextlayout.h> +#include <private/qobject_p.h> +#include <private/qdnd_p.h> +#include <private/qtextengine_p.h> +#include <qdebug.h> +#include <qlocale.h> +#include <qdialog.h> + +#include <limits.h> + +#ifndef DBL_DIG +# define DBL_DIG 10 +#endif + +QT_BEGIN_NAMESPACE + +class QItemDelegatePrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QItemDelegate) + +public: + QItemDelegatePrivate() : f(0), clipPainting(true) {} + + inline const QItemEditorFactory *editorFactory() const + { return f ? f : QItemEditorFactory::defaultFactory(); } + + inline QIcon::Mode iconMode(QStyle::State state) const + { + if (!(state & QStyle::State_Enabled)) return QIcon::Disabled; + if (state & QStyle::State_Selected) return QIcon::Selected; + return QIcon::Normal; + } + + inline QIcon::State iconState(QStyle::State state) const + { return state & QStyle::State_Open ? QIcon::On : QIcon::Off; } + + inline static QString replaceNewLine(QString text) + { + const QChar nl = QLatin1Char('\n'); + for (int i = 0; i < text.count(); ++i) + if (text.at(i) == nl) + text[i] = QChar::LineSeparator; + return text; + } + + static QString valueToText(const QVariant &value, const QStyleOptionViewItemV4 &option); + + void _q_commitDataAndCloseEditor(QWidget *editor); + + QItemEditorFactory *f; + bool clipPainting; + + QRect textLayoutBounds(const QStyleOptionViewItemV2 &options) const; + QSizeF doTextLayout(int lineWidth) const; + mutable QTextLayout textLayout; + mutable QTextOption textOption; + + const QWidget *widget(const QStyleOptionViewItem &option) const + { + if (const QStyleOptionViewItemV3 *v3 = qstyleoption_cast<const QStyleOptionViewItemV3 *>(&option)) + return v3->widget; + + return 0; + } + + // ### temporary hack until we have QStandardItemDelegate + mutable struct Icon { + QIcon icon; + QIcon::Mode mode; + QIcon::State state; + } tmp; +}; + +void QItemDelegatePrivate::_q_commitDataAndCloseEditor(QWidget *editor) +{ + Q_Q(QItemDelegate); + emit q->commitData(editor); + emit q->closeEditor(editor, QAbstractItemDelegate::SubmitModelCache); +} + +QRect QItemDelegatePrivate::textLayoutBounds(const QStyleOptionViewItemV2 &option) const +{ + QRect rect = option.rect; + const bool wrapText = option.features & QStyleOptionViewItemV2::WrapText; + switch (option.decorationPosition) { + case QStyleOptionViewItem::Left: + case QStyleOptionViewItem::Right: + rect.setWidth(wrapText && rect.isValid() ? rect.width() : (QFIXED_MAX)); + break; + case QStyleOptionViewItem::Top: + case QStyleOptionViewItem::Bottom: + rect.setWidth(wrapText ? option.decorationSize.width() : (QFIXED_MAX)); + break; + } + + return rect; +} + +QSizeF QItemDelegatePrivate::doTextLayout(int lineWidth) const +{ + qreal height = 0; + qreal widthUsed = 0; + textLayout.beginLayout(); + while (true) { + QTextLine line = textLayout.createLine(); + if (!line.isValid()) + break; + line.setLineWidth(lineWidth); + line.setPosition(QPointF(0, height)); + height += line.height(); + widthUsed = qMax(widthUsed, line.naturalTextWidth()); + } + textLayout.endLayout(); + return QSizeF(widthUsed, height); +} + +/*! + \class QItemDelegate + + \brief The QItemDelegate class provides display and editing facilities for + data items from a model. + + \ingroup model-view + \mainclass + + QItemDelegate can be used to provide custom display features and editor + widgets for item views based on QAbstractItemView subclasses. Using a + delegate for this purpose allows the display and editing mechanisms to be + customized and developed independently from the model and view. + + The QItemDelegate class is one of the \l{Model/View Classes} and + is part of Qt's \l{Model/View Programming}{model/view framework}. + Note that QStyledItemDelegate has taken over the job of drawing + Qt's item views. We recommend the use of QStyledItemDelegate when + creating new delegates. + + When displaying items from a custom model in a standard view, it is + often sufficient to simply ensure that the model returns appropriate + data for each of the \l{Qt::ItemDataRole}{roles} that determine the + appearance of items in views. The default delegate used by Qt's + standard views uses this role information to display items in most + of the common forms expected by users. However, it is sometimes + necessary to have even more control over the appearance of items than + the default delegate can provide. + + This class provides default implementations of the functions for + painting item data in a view and editing data from item models. + Default implementations of the paint() and sizeHint() virtual + functions, defined in QAbstractItemDelegate, are provided to + ensure that the delegate implements the correct basic behavior + expected by views. You can reimplement these functions in + subclasses to customize the appearance of items. + + When editing data in an item view, QItemDelegate provides an + editor widget, which is a widget that is placed on top of the view + while editing takes place. Editors are created with a + QItemEditorFactory; a default static instance provided by + QItemEditorFactory is installed on all item delagates. You can set + a custom factory using setItemEditorFactory() or set a new default + factory with QItemEditorFactory::setDefaultFactory(). It is the + data stored in the item model with the Qt::EditRole that is edited. + + Only the standard editing functions for widget-based delegates are + reimplemented here: + + \list + \o createEditor() returns the widget used to change data from the model + and can be reimplemented to customize editing behavior. + \o setEditorData() provides the widget with data to manipulate. + \o updateEditorGeometry() ensures that the editor is displayed correctly + with respect to the item view. + \o setModelData() returns updated data to the model. + \endlist + + The closeEditor() signal indicates that the user has completed editing the data, + and that the editor widget can be destroyed. + + \section1 Standard Roles and Data Types + + The default delegate used by the standard views supplied with Qt + associates each standard role (defined by Qt::ItemDataRole) with certain + data types. Models that return data in these types can influence the + appearance of the delegate as described in the following table. + + \table + \header \o Role \o Accepted Types + \omit + \row \o \l Qt::AccessibleDescriptionRole \o QString + \row \o \l Qt::AccessibleTextRole \o QString + \endomit + \row \o \l Qt::BackgroundRole \o QBrush + \row \o \l Qt::BackgroundColorRole \o QColor (obsolete; use Qt::BackgroundRole instead) + \row \o \l Qt::CheckStateRole \o Qt::CheckState + \row \o \l Qt::DecorationRole \o QIcon and QColor + \row \o \l Qt::DisplayRole \o QString and types with a string representation + \row \o \l Qt::EditRole \o See QItemEditorFactory for details + \row \o \l Qt::FontRole \o QFont + \row \o \l Qt::SizeHintRole \o QSize + \omit + \row \o \l Qt::StatusTipRole \o + \endomit + \row \o \l Qt::TextAlignmentRole \o Qt::Alignment + \row \o \l Qt::ForegroundRole \o QBrush + \row \o \l Qt::TextColorRole \o QColor (obsolete; use Qt::ForegroundRole instead) + \omit + \row \o \l Qt::ToolTipRole + \row \o \l Qt::WhatsThisRole + \endomit + \endtable + + If the default delegate does not allow the level of customization that + you need, either for display purposes or for editing data, it is possible to + subclass QItemDelegate to implement the desired behavior. + + \section1 Subclassing + + When subclassing QItemDelegate to create a delegate that displays items + using a custom renderer, it is important to ensure that the delegate can + render items suitably for all the required states; e.g. selected, + disabled, checked. The documentation for the paint() function contains + some hints to show how this can be achieved. + + You can provide custom editors by using a QItemEditorFactory. The + \l{Color Editor Factory Example} shows how a custom editor can be + made available to delegates with the default item editor + factory. This way, there is no need to subclass QItemDelegate. An + alternative is to reimplement createEditor(), setEditorData(), + setModelData(), and updateEditorGeometry(). This process is + described in the \l{Spin Box Delegate Example}. + + \sa {Delegate Classes}, QStyledItemDelegate, QAbstractItemDelegate, + {Spin Box Delegate Example}, {Settings Editor Example}, + {Icons Example} +*/ + +/*! + Constructs an item delegate with the given \a parent. +*/ + +QItemDelegate::QItemDelegate(QObject *parent) + : QAbstractItemDelegate(*new QItemDelegatePrivate(), parent) +{ + +} + +/*! + Destroys the item delegate. +*/ + +QItemDelegate::~QItemDelegate() +{ +} + +/*! + \property QItemDelegate::clipping + \brief if the delegate should clip the paint events + \since 4.2 + + This property will set the paint clip to the size of the item. + The default value is on. It is useful for cases such + as when images are larger than the size of the item. +*/ + +bool QItemDelegate::hasClipping() const +{ + Q_D(const QItemDelegate); + return d->clipPainting; +} + +void QItemDelegate::setClipping(bool clip) +{ + Q_D(QItemDelegate); + d->clipPainting = clip; +} + +QString QItemDelegatePrivate::valueToText(const QVariant &value, const QStyleOptionViewItemV4 &option) +{ + QString text; + switch (value.type()) { + case QVariant::Double: + text = option.locale.toString(value.toDouble(), 'g', DBL_DIG); + break; + case QVariant::Int: + case QVariant::LongLong: + text = option.locale.toString(value.toLongLong()); + break; + case QVariant::UInt: + case QVariant::ULongLong: + text = option.locale.toString(value.toULongLong()); + break; + case QVariant::Date: + text = option.locale.toString(value.toDate(), QLocale::ShortFormat); + break; + case QVariant::Time: + text = option.locale.toString(value.toTime(), QLocale::ShortFormat); + break; + case QVariant::DateTime: + text = option.locale.toString(value.toDateTime().date(), QLocale::ShortFormat); + text += QLatin1Char(' '); + text += option.locale.toString(value.toDateTime().time(), QLocale::ShortFormat); + break; + default: + text = replaceNewLine(value.toString()); + break; + } + return text; +} + +/*! + Renders the delegate using the given \a painter and style \a option for + the item specified by \a index. + + When reimplementing this function in a subclass, you should update the area + held by the option's \l{QStyleOption::rect}{rect} variable, using the + option's \l{QStyleOption::state}{state} variable to determine the state of + the item to be displayed, and adjust the way it is painted accordingly. + + For example, a selected item may need to be displayed differently to + unselected items, as shown in the following code: + + \snippet examples/itemviews/pixelator/pixeldelegate.cpp 2 + \dots + + After painting, you should ensure that the painter is returned to its + the state it was supplied in when this function was called. For example, + it may be useful to call QPainter::save() before painting and + QPainter::restore() afterwards. + + \sa QStyle::State +*/ +void QItemDelegate::paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + Q_D(const QItemDelegate); + Q_ASSERT(index.isValid()); + + QStyleOptionViewItemV4 opt = setOptions(index, option); + + const QStyleOptionViewItemV2 *v2 = qstyleoption_cast<const QStyleOptionViewItemV2 *>(&option); + opt.features = v2 ? v2->features + : QStyleOptionViewItemV2::ViewItemFeatures(QStyleOptionViewItemV2::None); + const QStyleOptionViewItemV3 *v3 = qstyleoption_cast<const QStyleOptionViewItemV3 *>(&option); + opt.locale = v3 ? v3->locale : QLocale(); + opt.widget = v3 ? v3->widget : 0; + + // prepare + painter->save(); + if (d->clipPainting) + painter->setClipRect(opt.rect); + + // get the data and the rectangles + + QVariant value; + + QPixmap pixmap; + QRect decorationRect; + value = index.data(Qt::DecorationRole); + if (value.isValid()) { + // ### we need the pixmap to call the virtual function + pixmap = decoration(opt, value); + if (value.type() == QVariant::Icon) { + d->tmp.icon = qvariant_cast<QIcon>(value); + d->tmp.mode = d->iconMode(option.state); + d->tmp.state = d->iconState(option.state); + const QSize size = d->tmp.icon.actualSize(option.decorationSize, + d->tmp.mode, d->tmp.state); + decorationRect = QRect(QPoint(0, 0), size); + } else { + d->tmp.icon = QIcon(); + decorationRect = QRect(QPoint(0, 0), pixmap.size()); + } + } else { + d->tmp.icon = QIcon(); + decorationRect = QRect(); + } + + QString text; + QRect displayRect; + value = index.data(Qt::DisplayRole); + if (value.isValid() && !value.isNull()) { + text = QItemDelegatePrivate::valueToText(value, opt); + displayRect = textRectangle(painter, d->textLayoutBounds(opt), opt.font, text); + } + + QRect checkRect; + Qt::CheckState checkState = Qt::Unchecked; + value = index.data(Qt::CheckStateRole); + if (value.isValid()) { + checkState = static_cast<Qt::CheckState>(value.toInt()); + checkRect = check(opt, opt.rect, value); + } + + // do the layout + + doLayout(opt, &checkRect, &decorationRect, &displayRect, false); + + // draw the item + + drawBackground(painter, opt, index); + drawCheck(painter, opt, checkRect, checkState); + drawDecoration(painter, opt, decorationRect, pixmap); + drawDisplay(painter, opt, displayRect, text); + drawFocus(painter, opt, displayRect); + + // done + painter->restore(); +} + +/*! + Returns the size needed by the delegate to display the item + specified by \a index, taking into account the style information + provided by \a option. + + When reimplementing this function, note that in case of text + items, QItemDelegate adds a margin (i.e. 2 * + QStyle::PM_FocusFrameHMargin) to the length of the text. +*/ + +QSize QItemDelegate::sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QVariant value = index.data(Qt::SizeHintRole); + if (value.isValid()) + return qvariant_cast<QSize>(value); + QRect decorationRect = rect(option, index, Qt::DecorationRole); + QRect displayRect = rect(option, index, Qt::DisplayRole); + QRect checkRect = rect(option, index, Qt::CheckStateRole); + + doLayout(option, &checkRect, &decorationRect, &displayRect, true); + + return (decorationRect|displayRect|checkRect).size(); +} + +/*! + Returns the widget used to edit the item specified by \a index + for editing. The \a parent widget and style \a option are used to + control how the editor widget appears. + + \sa QAbstractItemDelegate::createEditor() +*/ + +QWidget *QItemDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem &, + const QModelIndex &index) const +{ + Q_D(const QItemDelegate); + if (!index.isValid()) + return 0; + QVariant::Type t = static_cast<QVariant::Type>(index.data(Qt::EditRole).userType()); + const QItemEditorFactory *factory = d->f; + if (factory == 0) + factory = QItemEditorFactory::defaultFactory(); + return factory->createEditor(t, parent); +} + +/*! + Sets the data to be displayed and edited by the \a editor from the + data model item specified by the model \a index. + + The default implementation stores the data in the \a editor + widget's \l {Qt's Property System} {user property}. + + \sa QMetaProperty::isUser() +*/ + +void QItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const +{ +#ifdef QT_NO_PROPERTIES + Q_UNUSED(editor); + Q_UNUSED(index); +#else + Q_D(const QItemDelegate); + QVariant v = index.data(Qt::EditRole); + QByteArray n = editor->metaObject()->userProperty().name(); + + // ### Qt 5: remove + // A work-around for missing "USER true" in qdatetimeedit.h for + // QTimeEdit's time property and QDateEdit's date property. + // It only triggers if the default user property "dateTime" is + // reported for QTimeEdit and QDateEdit. + if (n == "dateTime") { + if (editor->inherits("QTimeEdit")) + n = "time"; + else if (editor->inherits("QDateEdit")) + n = "date"; + } + + // ### Qt 5: give QComboBox a USER property + if (n.isEmpty() && editor->inherits("QComboBox")) + n = d->editorFactory()->valuePropertyName(static_cast<QVariant::Type>(v.userType())); + if (!n.isEmpty()) { + if (!v.isValid()) + v = QVariant(editor->property(n).userType(), (const void *)0); + editor->setProperty(n, v); + } +#endif +} + +/*! + Gets data drom the \a editor widget and stores it in the specified + \a model at the item \a index. + + The default implementation gets the value to be stored in the data + model from the \a editor widget's \l {Qt's Property System} {user + property}. + + \sa QMetaProperty::isUser() +*/ + +void QItemDelegate::setModelData(QWidget *editor, + QAbstractItemModel *model, + const QModelIndex &index) const +{ +#ifdef QT_NO_PROPERTIES + Q_UNUSED(model); + Q_UNUSED(editor); + Q_UNUSED(index); +#else + Q_D(const QItemDelegate); + Q_ASSERT(model); + Q_ASSERT(editor); + QByteArray n = editor->metaObject()->userProperty().name(); + if (n.isEmpty()) + n = d->editorFactory()->valuePropertyName( + static_cast<QVariant::Type>(model->data(index, Qt::EditRole).userType())); + if (!n.isEmpty()) + model->setData(index, editor->property(n), Qt::EditRole); +#endif +} + +/*! + Updates the \a editor for the item specified by \a index + according to the style \a option given. +*/ + +void QItemDelegate::updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + if (!editor) + return; + Q_ASSERT(index.isValid()); + QPixmap pixmap = decoration(option, index.data(Qt::DecorationRole)); + QString text = QItemDelegatePrivate::replaceNewLine(index.data(Qt::DisplayRole).toString()); + QRect pixmapRect = QRect(QPoint(0, 0), option.decorationSize).intersected(pixmap.rect()); + QRect textRect = textRectangle(0, option.rect, option.font, text); + QRect checkRect = check(option, textRect, index.data(Qt::CheckStateRole)); + QStyleOptionViewItem opt = option; + opt.showDecorationSelected = true; // let the editor take up all available space + doLayout(opt, &checkRect, &pixmapRect, &textRect, false); + editor->setGeometry(textRect); +} + +/*! + Returns the editor factory used by the item delegate. + If no editor factory is set, the function will return null. + + \sa setItemEditorFactory() +*/ +QItemEditorFactory *QItemDelegate::itemEditorFactory() const +{ + Q_D(const QItemDelegate); + return d->f; +} + +/*! + Sets the editor factory to be used by the item delegate to be the \a factory + specified. If no editor factory is set, the item delegate will use the + default editor factory. + + \sa itemEditorFactory() +*/ +void QItemDelegate::setItemEditorFactory(QItemEditorFactory *factory) +{ + Q_D(QItemDelegate); + d->f = factory; +} + +/*! + Renders the item view \a text within the rectangle specified by \a rect + using the given \a painter and style \a option. +*/ + +void QItemDelegate::drawDisplay(QPainter *painter, const QStyleOptionViewItem &option, + const QRect &rect, const QString &text) const +{ + Q_D(const QItemDelegate); + + QPen pen = painter->pen(); + QPalette::ColorGroup cg = option.state & QStyle::State_Enabled + ? QPalette::Normal : QPalette::Disabled; + if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) + cg = QPalette::Inactive; + if (option.state & QStyle::State_Selected) { + painter->fillRect(rect, option.palette.brush(cg, QPalette::Highlight)); + painter->setPen(option.palette.color(cg, QPalette::HighlightedText)); + } else { + painter->setPen(option.palette.color(cg, QPalette::Text)); + } + + if (text.isEmpty()) + return; + + if (option.state & QStyle::State_Editing) { + painter->save(); + painter->setPen(option.palette.color(cg, QPalette::Text)); + painter->drawRect(rect.adjusted(0, 0, -1, -1)); + painter->restore(); + } + + const QStyleOptionViewItemV4 opt = option; + + const QWidget *widget = d->widget(option); + QStyle *style = widget ? widget->style() : QApplication::style(); + const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, widget) + 1; + QRect textRect = rect.adjusted(textMargin, 0, -textMargin, 0); // remove width padding + const bool wrapText = opt.features & QStyleOptionViewItemV2::WrapText; + d->textOption.setWrapMode(wrapText ? QTextOption::WordWrap : QTextOption::ManualWrap); + d->textOption.setTextDirection(option.direction); + d->textOption.setAlignment(QStyle::visualAlignment(option.direction, option.displayAlignment)); + d->textLayout.setTextOption(d->textOption); + d->textLayout.setFont(option.font); + d->textLayout.setText(QItemDelegatePrivate::replaceNewLine(text)); + + QSizeF textLayoutSize = d->doTextLayout(textRect.width()); + + if (textRect.width() < textLayoutSize.width() + || textRect.height() < textLayoutSize.height()) { + QString elided; + int start = 0; + int end = text.indexOf(QChar::LineSeparator, start); + if (end == -1) { + elided += option.fontMetrics.elidedText(text, option.textElideMode, textRect.width()); + } else { + while (end != -1) { + elided += option.fontMetrics.elidedText(text.mid(start, end - start), + option.textElideMode, textRect.width()); + elided += QChar::LineSeparator; + start = end + 1; + end = text.indexOf(QChar::LineSeparator, start); + } + //let's add the last line (after the last QChar::LineSeparator) + elided += option.fontMetrics.elidedText(text.mid(start), + option.textElideMode, textRect.width()); + if (end != -1) + elided += QChar::LineSeparator; + } + d->textLayout.setText(elided); + textLayoutSize = d->doTextLayout(textRect.width()); + } + + const QSize layoutSize(textRect.width(), int(textLayoutSize.height())); + const QRect layoutRect = QStyle::alignedRect(option.direction, option.displayAlignment, + layoutSize, textRect); + // if we still overflow even after eliding the text, enable clipping + if (!hasClipping() && (textRect.width() < textLayoutSize.width() + || textRect.height() < textLayoutSize.height())) { + painter->save(); + painter->setClipRect(layoutRect); + d->textLayout.draw(painter, layoutRect.topLeft(), QVector<QTextLayout::FormatRange>(), layoutRect); + painter->restore(); + } else { + d->textLayout.draw(painter, layoutRect.topLeft(), QVector<QTextLayout::FormatRange>(), layoutRect); + } +} + +/*! + Renders the decoration \a pixmap within the rectangle specified by + \a rect using the given \a painter and style \a option. +*/ +void QItemDelegate::drawDecoration(QPainter *painter, const QStyleOptionViewItem &option, + const QRect &rect, const QPixmap &pixmap) const +{ + Q_D(const QItemDelegate); + // if we have an icon, we ignore the pixmap + if (!d->tmp.icon.isNull()) { + d->tmp.icon.paint(painter, rect, option.decorationAlignment, + d->tmp.mode, d->tmp.state); + return; + } + + if (pixmap.isNull() || !rect.isValid()) + return; + QPoint p = QStyle::alignedRect(option.direction, option.decorationAlignment, + pixmap.size(), rect).topLeft(); + if (option.state & QStyle::State_Selected) { + QPixmap *pm = selected(pixmap, option.palette, option.state & QStyle::State_Enabled); + painter->drawPixmap(p, *pm); + } else { + painter->drawPixmap(p, pixmap); + } +} + +/*! + Renders the region within the rectangle specified by \a rect, indicating + that it has the focus, using the given \a painter and style \a option. +*/ + +void QItemDelegate::drawFocus(QPainter *painter, + const QStyleOptionViewItem &option, + const QRect &rect) const +{ + Q_D(const QItemDelegate); + if ((option.state & QStyle::State_HasFocus) == 0 || !rect.isValid()) + return; + QStyleOptionFocusRect o; + o.QStyleOption::operator=(option); + o.rect = rect; + o.state |= QStyle::State_KeyboardFocusChange; + o.state |= QStyle::State_Item; + QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) + ? QPalette::Normal : QPalette::Disabled; + o.backgroundColor = option.palette.color(cg, (option.state & QStyle::State_Selected) + ? QPalette::Highlight : QPalette::Window); + const QWidget *widget = d->widget(option); + QStyle *style = widget ? widget->style() : QApplication::style(); + style->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter, widget); +} + +/*! + Renders a check indicator within the rectangle specified by \a + rect, using the given \a painter and style \a option, using the + given \a state. +*/ + +void QItemDelegate::drawCheck(QPainter *painter, + const QStyleOptionViewItem &option, + const QRect &rect, Qt::CheckState state) const +{ + Q_D(const QItemDelegate); + if (!rect.isValid()) + return; + + QStyleOptionViewItem opt(option); + opt.rect = rect; + opt.state = opt.state & ~QStyle::State_HasFocus; + + switch (state) { + case Qt::Unchecked: + opt.state |= QStyle::State_Off; + break; + case Qt::PartiallyChecked: + opt.state |= QStyle::State_NoChange; + break; + case Qt::Checked: + opt.state |= QStyle::State_On; + break; + } + + const QWidget *widget = d->widget(option); + QStyle *style = widget ? widget->style() : QApplication::style(); + style->drawPrimitive(QStyle::PE_IndicatorViewItemCheck, &opt, painter, widget); +} + +/*! + \since 4.2 + + Renders the item background for the given \a index, + using the given \a painter and style \a option. +*/ + +void QItemDelegate::drawBackground(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + if (option.showDecorationSelected && (option.state & QStyle::State_Selected)) { + QPalette::ColorGroup cg = option.state & QStyle::State_Enabled + ? QPalette::Normal : QPalette::Disabled; + if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) + cg = QPalette::Inactive; + + painter->fillRect(option.rect, option.palette.brush(cg, QPalette::Highlight)); + } else { + QVariant value = index.data(Qt::BackgroundRole); + if (qVariantCanConvert<QBrush>(value)) { + QPointF oldBO = painter->brushOrigin(); + painter->setBrushOrigin(option.rect.topLeft()); + painter->fillRect(option.rect, qvariant_cast<QBrush>(value)); + painter->setBrushOrigin(oldBO); + } + } +} + + +/*! + \internal +*/ + +void QItemDelegate::doLayout(const QStyleOptionViewItem &option, + QRect *checkRect, QRect *pixmapRect, QRect *textRect, + bool hint) const +{ + Q_ASSERT(checkRect && pixmapRect && textRect); + Q_D(const QItemDelegate); + const QWidget *widget = d->widget(option); + QStyle *style = widget ? widget->style() : QApplication::style(); + const bool hasCheck = checkRect->isValid(); + const bool hasPixmap = pixmapRect->isValid(); + const bool hasText = textRect->isValid(); + const int textMargin = hasText ? style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, widget) + 1 : 0; + const int pixmapMargin = hasPixmap ? style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, widget) + 1 : 0; + const int checkMargin = hasCheck ? style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, widget) + 1 : 0; + int x = option.rect.left(); + int y = option.rect.top(); + int w, h; + + textRect->adjust(-textMargin, 0, textMargin, 0); // add width padding + if (textRect->height() == 0 && !hasPixmap) + textRect->setHeight(option.fontMetrics.height()); + + QSize pm(0, 0); + if (hasPixmap) { + pm = pixmapRect->size(); + pm.rwidth() += 2 * pixmapMargin; + } + if (hint) { + h = qMax(checkRect->height(), qMax(textRect->height(), pm.height())); + if (option.decorationPosition == QStyleOptionViewItem::Left + || option.decorationPosition == QStyleOptionViewItem::Right) { + w = textRect->width() + pm.width(); + } else { + w = qMax(textRect->width(), pm.width()); + } + } else { + w = option.rect.width(); + h = option.rect.height(); + } + + int cw = 0; + QRect check; + if (hasCheck) { + cw = checkRect->width() + 2 * checkMargin; + if (hint) w += cw; + if (option.direction == Qt::RightToLeft) { + check.setRect(x + w - cw, y, cw, h); + } else { + check.setRect(x + checkMargin, y, cw, h); + } + } + + // at this point w should be the *total* width + + QRect display; + QRect decoration; + switch (option.decorationPosition) { + case QStyleOptionViewItem::Top: { + if (hasPixmap) + pm.setHeight(pm.height() + pixmapMargin); // add space + h = hint ? textRect->height() : h - pm.height(); + + if (option.direction == Qt::RightToLeft) { + decoration.setRect(x, y, w - cw, pm.height()); + display.setRect(x, y + pm.height(), w - cw, h); + } else { + decoration.setRect(x + cw, y, w - cw, pm.height()); + display.setRect(x + cw, y + pm.height(), w - cw, h); + } + break; } + case QStyleOptionViewItem::Bottom: { + if (hasText) + textRect->setHeight(textRect->height() + textMargin); // add space + h = hint ? textRect->height() + pm.height() : h; + + if (option.direction == Qt::RightToLeft) { + display.setRect(x, y, w - cw, textRect->height()); + decoration.setRect(x, y + textRect->height(), w - cw, h - textRect->height()); + } else { + display.setRect(x + cw, y, w - cw, textRect->height()); + decoration.setRect(x + cw, y + textRect->height(), w - cw, h - textRect->height()); + } + break; } + case QStyleOptionViewItem::Left: { + if (option.direction == Qt::LeftToRight) { + decoration.setRect(x + cw, y, pm.width(), h); + display.setRect(decoration.right() + 1, y, w - pm.width() - cw, h); + } else { + display.setRect(x, y, w - pm.width() - cw, h); + decoration.setRect(display.right() + 1, y, pm.width(), h); + } + break; } + case QStyleOptionViewItem::Right: { + if (option.direction == Qt::LeftToRight) { + display.setRect(x + cw, y, w - pm.width() - cw, h); + decoration.setRect(display.right() + 1, y, pm.width(), h); + } else { + decoration.setRect(x, y, pm.width(), h); + display.setRect(decoration.right() + 1, y, w - pm.width() - cw, h); + } + break; } + default: + qWarning("doLayout: decoration position is invalid"); + decoration = *pixmapRect; + break; + } + + if (!hint) { // we only need to do the internal layout if we are going to paint + *checkRect = QStyle::alignedRect(option.direction, Qt::AlignCenter, + checkRect->size(), check); + *pixmapRect = QStyle::alignedRect(option.direction, option.decorationAlignment, + pixmapRect->size(), decoration); + // the text takes up all available space, unless the decoration is not shown as selected + if (option.showDecorationSelected) + *textRect = display; + else + *textRect = QStyle::alignedRect(option.direction, option.displayAlignment, + textRect->size().boundedTo(display.size()), display); + } else { + *checkRect = check; + *pixmapRect = decoration; + *textRect = display; + } +} + +/*! + \internal + + Returns the pixmap used to decorate the root of the item view. + The style \a option controls the appearance of the root; the \a variant + refers to the data associated with an item. +*/ + +QPixmap QItemDelegate::decoration(const QStyleOptionViewItem &option, const QVariant &variant) const +{ + Q_D(const QItemDelegate); + switch (variant.type()) { + case QVariant::Icon: { + QIcon::Mode mode = d->iconMode(option.state); + QIcon::State state = d->iconState(option.state); + return qvariant_cast<QIcon>(variant).pixmap(option.decorationSize, mode, state); } + case QVariant::Color: { + static QPixmap pixmap(option.decorationSize); + pixmap.fill(qvariant_cast<QColor>(variant)); + return pixmap; } + default: + break; + } + + return qvariant_cast<QPixmap>(variant); +} + +// hacky but faster version of "QString::sprintf("%d-%d", i, enabled)" +static QString qPixmapSerial(quint64 i, bool enabled) +{ + ushort arr[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '-', '0' + enabled }; + ushort *ptr = &arr[16]; + + while (i > 0) { + // hey - it's our internal representation, so use the ascii character after '9' + // instead of 'a' for hex + *(--ptr) = '0' + i % 16; + i >>= 4; + } + + return QString::fromUtf16(ptr, int(&arr[sizeof(arr) / sizeof(ushort)] - ptr)); +} + +/*! + \internal + Returns the selected version of the given \a pixmap using the given \a palette. + The \a enabled argument decides whether the normal or disabled highlight color of + the palette is used. +*/ +QPixmap *QItemDelegate::selected(const QPixmap &pixmap, const QPalette &palette, bool enabled) const +{ + QString key = qPixmapSerial(qt_pixmap_id(pixmap), enabled); + QPixmap *pm = QPixmapCache::find(key); + if (!pm) { + QImage img = pixmap.toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied); + + QColor color = palette.color(enabled ? QPalette::Normal : QPalette::Disabled, + QPalette::Highlight); + color.setAlphaF((qreal)0.3); + + QPainter painter(&img); + painter.setCompositionMode(QPainter::CompositionMode_SourceAtop); + painter.fillRect(0, 0, img.width(), img.height(), color); + painter.end(); + + QPixmap selected = QPixmap(QPixmap::fromImage(img)); + int n = (img.numBytes() >> 10) + 1; + if (QPixmapCache::cacheLimit() < n) + QPixmapCache::setCacheLimit(n); + + QPixmapCache::insert(key, selected); + pm = QPixmapCache::find(key); + } + return pm; +} + +/*! + \internal +*/ + +QRect QItemDelegate::rect(const QStyleOptionViewItem &option, + const QModelIndex &index, int role) const +{ + Q_D(const QItemDelegate); + QVariant value = index.data(role); + if (role == Qt::CheckStateRole) + return check(option, option.rect, value); + if (value.isValid() && !value.isNull()) { + switch (value.type()) { + case QVariant::Invalid: + break; + case QVariant::Pixmap: + return QRect(QPoint(0, 0), qvariant_cast<QPixmap>(value).size()); + case QVariant::Image: + return QRect(QPoint(0, 0), qvariant_cast<QImage>(value).size()); + case QVariant::Icon: { + QIcon::Mode mode = d->iconMode(option.state); + QIcon::State state = d->iconState(option.state); + QIcon icon = qvariant_cast<QIcon>(value); + QSize size = icon.actualSize(option.decorationSize, mode, state); + return QRect(QPoint(0, 0), size); } + case QVariant::Color: + return QRect(QPoint(0, 0), option.decorationSize); + case QVariant::String: + default: { + QString text = QItemDelegatePrivate::valueToText(value, option); + value = index.data(Qt::FontRole); + QFont fnt = qvariant_cast<QFont>(value).resolve(option.font); + return textRectangle(0, d->textLayoutBounds(option), fnt, text); } + } + } + return QRect(); +} + +/*! + \internal + + Note that on Mac, if /usr/include/AssertMacros.h is included prior + to QItemDelegate, and the application is building in debug mode, the + check(assertion) will conflict with QItemDelegate::check. + + To avoid this problem, add + + #ifdef check + #undef check + #endif + + after including AssertMacros.h +*/ +QRect QItemDelegate::check(const QStyleOptionViewItem &option, + const QRect &bounding, const QVariant &value) const +{ + if (value.isValid()) { + Q_D(const QItemDelegate); + QStyleOptionButton opt; + opt.QStyleOption::operator=(option); + opt.rect = bounding; + const QWidget *widget = d->widget(option); // cast + QStyle *style = widget ? widget->style() : QApplication::style(); + return style->subElementRect(QStyle::SE_ViewItemCheckIndicator, &opt, widget); + } + return QRect(); +} + +/*! + \internal +*/ +QRect QItemDelegate::textRectangle(QPainter * /*painter*/, const QRect &rect, + const QFont &font, const QString &text) const +{ + Q_D(const QItemDelegate); + d->textOption.setWrapMode(QTextOption::WordWrap); + d->textLayout.setTextOption(d->textOption); + d->textLayout.setFont(font); + d->textLayout.setText(QItemDelegatePrivate::replaceNewLine(text)); + const QSize size = d->doTextLayout(rect.width()).toSize(); + // ###: textRectangle should take style option as argument + const int textMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; + return QRect(0, 0, size.width() + 2 * textMargin, size.height()); +} + +/*! + \fn bool QItemDelegate::eventFilter(QObject *editor, QEvent *event) + + Returns true if the given \a editor is a valid QWidget and the + given \a event is handled; otherwise returns false. The following + key press events are handled by default: + + \list + \o \gui Tab + \o \gui Backtab + \o \gui Enter + \o \gui Return + \o \gui Esc + \endlist + + In the case of \gui Tab, \gui Backtab, \gui Enter and \gui Return + key press events, the \a editor's data is comitted to the model + and the editor is closed. If the \a event is a \gui Tab key press + the view will open an editor on the next item in the + view. Likewise, if the \a event is a \gui Backtab key press the + view will open an editor on the \e previous item in the view. + + If the event is a \gui Esc key press event, the \a editor is + closed \e without committing its data. + + \sa commitData(), closeEditor() +*/ + +bool QItemDelegate::eventFilter(QObject *object, QEvent *event) +{ + QWidget *editor = qobject_cast<QWidget*>(object); + if (!editor) + return false; + if (event->type() == QEvent::KeyPress) { + switch (static_cast<QKeyEvent *>(event)->key()) { + case Qt::Key_Tab: + emit commitData(editor); + emit closeEditor(editor, QAbstractItemDelegate::EditNextItem); + return true; + case Qt::Key_Backtab: + emit commitData(editor); + emit closeEditor(editor, QAbstractItemDelegate::EditPreviousItem); + return true; + case Qt::Key_Enter: + case Qt::Key_Return: +#ifndef QT_NO_TEXTEDIT + if (qobject_cast<QTextEdit*>(editor)) + return false; // don't filter enter key events for QTextEdit + // We want the editor to be able to process the key press + // before committing the data (e.g. so it can do + // validation/fixup of the input). +#endif // QT_NO_TEXTEDIT +#ifndef QT_NO_LINEEDIT + if (QLineEdit *e = qobject_cast<QLineEdit*>(editor)) + if (!e->hasAcceptableInput()) + return false; +#endif // QT_NO_LINEEDIT + QMetaObject::invokeMethod(this, "_q_commitDataAndCloseEditor", + Qt::QueuedConnection, Q_ARG(QWidget*, editor)); + return false; + case Qt::Key_Escape: + // don't commit data + emit closeEditor(editor, QAbstractItemDelegate::RevertModelCache); + break; + default: + return false; + } + if (editor->parentWidget()) + editor->parentWidget()->setFocus(); + return true; + } else if (event->type() == QEvent::FocusOut || event->type() == QEvent::Hide) { + //the Hide event will take care of he editors that are in fact complete dialogs + if (!editor->isActiveWindow() || (QApplication::focusWidget() != editor)) { + QWidget *w = QApplication::focusWidget(); + while (w) { // don't worry about focus changes internally in the editor + if (w == editor) + return false; + w = w->parentWidget(); + } +#ifndef QT_NO_DRAGANDDROP + // The window may lose focus during an drag operation. + // i.e when dragging involves the taskbar on Windows. + if (QDragManager::self() && QDragManager::self()->object != 0) + return false; +#endif + // Opening a modal dialog will start a new eventloop + // that will process the deleteLater event. + if (QApplication::activeModalWidget() + && !QApplication::activeModalWidget()->isAncestorOf(editor) + && qobject_cast<QDialog*>(QApplication::activeModalWidget())) + return false; + emit commitData(editor); + emit closeEditor(editor, NoHint); + } + } else if (event->type() == QEvent::ShortcutOverride) { + if (static_cast<QKeyEvent*>(event)->key() == Qt::Key_Escape) { + event->accept(); + return true; + } + } + return false; +} + +/*! + \reimp +*/ + +bool QItemDelegate::editorEvent(QEvent *event, + QAbstractItemModel *model, + const QStyleOptionViewItem &option, + const QModelIndex &index) +{ + Q_ASSERT(event); + Q_ASSERT(model); + + // make sure that the item is checkable + Qt::ItemFlags flags = model->flags(index); + if (!(flags & Qt::ItemIsUserCheckable) || !(option.state & QStyle::State_Enabled) + || !(flags & Qt::ItemIsEnabled)) + return false; + + // make sure that we have a check state + QVariant value = index.data(Qt::CheckStateRole); + if (!value.isValid()) + return false; + + // make sure that we have the right event type + if ((event->type() == QEvent::MouseButtonRelease) + || (event->type() == QEvent::MouseButtonDblClick)) { + QRect checkRect = check(option, option.rect, Qt::Checked); + QRect emptyRect; + doLayout(option, &checkRect, &emptyRect, &emptyRect, false); + QMouseEvent *me = static_cast<QMouseEvent*>(event); + if (me->button() != Qt::LeftButton || !checkRect.contains(me->pos())) + return false; + + // eat the double click events inside the check rect + if (event->type() == QEvent::MouseButtonDblClick) + return true; + + } else if (event->type() == QEvent::KeyPress) { + if (static_cast<QKeyEvent*>(event)->key() != Qt::Key_Space + && static_cast<QKeyEvent*>(event)->key() != Qt::Key_Select) + return false; + } else { + return false; + } + + Qt::CheckState state = (static_cast<Qt::CheckState>(value.toInt()) == Qt::Checked + ? Qt::Unchecked : Qt::Checked); + return model->setData(index, state, Qt::CheckStateRole); +} + +/*! + \internal +*/ + +QStyleOptionViewItem QItemDelegate::setOptions(const QModelIndex &index, + const QStyleOptionViewItem &option) const +{ + QStyleOptionViewItem opt = option; + + // set font + QVariant value = index.data(Qt::FontRole); + if (value.isValid()){ + opt.font = qvariant_cast<QFont>(value).resolve(opt.font); + opt.fontMetrics = QFontMetrics(opt.font); + } + + // set text alignment + value = index.data(Qt::TextAlignmentRole); + if (value.isValid()) + opt.displayAlignment = (Qt::Alignment)value.toInt(); + + // set foreground brush + value = index.data(Qt::ForegroundRole); + if (qVariantCanConvert<QBrush>(value)) + opt.palette.setBrush(QPalette::Text, qvariant_cast<QBrush>(value)); + + return opt; +} + +QT_END_NAMESPACE + +#include "moc_qitemdelegate.cpp" + +#endif // QT_NO_ITEMVIEWS diff --git a/src/gui/itemviews/qitemdelegate.h b/src/gui/itemviews/qitemdelegate.h new file mode 100644 index 0000000..8b286ac --- /dev/null +++ b/src/gui/itemviews/qitemdelegate.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QITEMDELEGATE_H +#define QITEMDELEGATE_H + +#include <QtGui/qabstractitemdelegate.h> +#include <QtCore/qstring.h> +#include <QtGui/qpixmap.h> +#include <QtCore/qvariant.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_ITEMVIEWS + +class QItemDelegatePrivate; +class QItemEditorFactory; + +class Q_GUI_EXPORT QItemDelegate : public QAbstractItemDelegate +{ + Q_OBJECT + Q_PROPERTY(bool clipping READ hasClipping WRITE setClipping) + +public: + explicit QItemDelegate(QObject *parent = 0); + ~QItemDelegate(); + + bool hasClipping() const; + void setClipping(bool clip); + + // painting + void paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const; + QSize sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + // editing + QWidget *createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + void setEditorData(QWidget *editor, const QModelIndex &index) const; + void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const; + + void updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + // editor factory + QItemEditorFactory *itemEditorFactory() const; + void setItemEditorFactory(QItemEditorFactory *factory); + +protected: + virtual void drawDisplay(QPainter *painter, const QStyleOptionViewItem &option, + const QRect &rect, const QString &text) const; + virtual void drawDecoration(QPainter *painter, const QStyleOptionViewItem &option, + const QRect &rect, const QPixmap &pixmap) const; + virtual void drawFocus(QPainter *painter, const QStyleOptionViewItem &option, + const QRect &rect) const; + virtual void drawCheck(QPainter *painter, const QStyleOptionViewItem &option, + const QRect &rect, Qt::CheckState state) const; + void drawBackground(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + void doLayout(const QStyleOptionViewItem &option, + QRect *checkRect, QRect *iconRect, QRect *textRect, bool hint) const; + + QRect rect(const QStyleOptionViewItem &option, const QModelIndex &index, int role) const; + + bool eventFilter(QObject *object, QEvent *event); + bool editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, const QModelIndex &index); + + QStyleOptionViewItem setOptions(const QModelIndex &index, + const QStyleOptionViewItem &option) const; + + QPixmap decoration(const QStyleOptionViewItem &option, const QVariant &variant) const; + QPixmap *selected(const QPixmap &pixmap, const QPalette &palette, bool enabled) const; + + QRect check(const QStyleOptionViewItem &option, const QRect &bounding, + const QVariant &variant) const; + QRect textRectangle(QPainter *painter, const QRect &rect, + const QFont &font, const QString &text) const; + +private: + Q_DECLARE_PRIVATE(QItemDelegate) + Q_DISABLE_COPY(QItemDelegate) + + Q_PRIVATE_SLOT(d_func(), void _q_commitDataAndCloseEditor(QWidget*)) +}; + +#endif // QT_NO_ITEMVIEWS + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QITEMDELEGATE_H diff --git a/src/gui/itemviews/qitemeditorfactory.cpp b/src/gui/itemviews/qitemeditorfactory.cpp new file mode 100644 index 0000000..aefdea1 --- /dev/null +++ b/src/gui/itemviews/qitemeditorfactory.cpp @@ -0,0 +1,566 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qplatformdefs.h> +#include "qitemeditorfactory.h" +#include "qitemeditorfactory_p.h" + +#ifndef QT_NO_ITEMVIEWS + +#include <qcombobox.h> +#include <qdatetimeedit.h> +#include <qlabel.h> +#include <qlineedit.h> +#include <qspinbox.h> +#include <limits.h> +#include <float.h> +#include <qapplication.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + + +#ifndef QT_NO_COMBOBOX + +class QBooleanComboBox : public QComboBox +{ + Q_OBJECT + Q_PROPERTY(bool value READ value WRITE setValue USER true) + +public: + QBooleanComboBox(QWidget *parent); + void setValue(bool); + bool value() const; +}; + +#endif // QT_NO_COMBOBOX + +/*! + \class QItemEditorFactory + \brief The QItemEditorFactory class provides widgets for editing item data + in views and delegates. + \since 4.2 + \ingroup model-view + + When editing data in an item view, editors are created and + displayed by a delegate. QItemDelegate, which is the delegate by + default installed on Qt's item views, uses a QItemEditorFactory to + create editors for it. A default unique instance provided by + QItemEditorFactory is used by all item delegates. If you set a + new default factory with setDefaultFactory(), the new factory will + be used by existing and new delegates. + + A factory keeps a collection of QItemEditorCreatorBase + instances, which are specialized editors that produce editors + for one particular QVariant data type (All Qt models store + their data in \l{QVariant}s). + + \section1 Standard Editing Widgets + + The standard factory implementation provides editors for a variety of data + types. These are created whenever a delegate needs to provide an editor for + data supplied by a model. The following table shows the relationship between + types and the standard editors provided. + + \table + \header \o Type \o Editor Widget + \row \o bool \o QComboBox + \row \o double \o QDoubleSpinBox + \row \o int \o{1,2} QSpinBox + \row \o unsigned int + \row \o QDate \o QDateEdit + \row \o QDateTime \o QDateTimeEdit + \row \o QPixmap \o QLabel + \row \o QString \o QLineEdit + \row \o QTime \o QTimeEdit + \endtable + + Additional editors can be registered with the registerEditor() function. + + \sa QItemDelegate, {Model/View Programming}, {Color Editor Factory Example} +*/ + +/*! + \fn QItemEditorFactory::QItemEditorFactory() + + Constructs a new item editor factory. +*/ + +/*! + Creates an editor widget with the given \a parent for the specified \a type of data, + and returns it as a QWidget. + + \sa registerEditor() +*/ +QWidget *QItemEditorFactory::createEditor(QVariant::Type type, QWidget *parent) const +{ + QItemEditorCreatorBase *creator = creatorMap.value(type, 0); + if (!creator) { + const QItemEditorFactory *dfactory = defaultFactory(); + return dfactory == this ? 0 : dfactory->createEditor(type, parent); + } + return creator->createWidget(parent); +} + +/*! + Returns the property name used to access data for the given \a type of data. +*/ +QByteArray QItemEditorFactory::valuePropertyName(QVariant::Type type) const +{ + QItemEditorCreatorBase *creator = creatorMap.value(type, 0); + if (!creator) { + const QItemEditorFactory *dfactory = defaultFactory(); + return dfactory == this ? QByteArray() : dfactory->valuePropertyName(type); + } + return creator->valuePropertyName(); +} + +/*! + Destroys the item editor factory. +*/ +QItemEditorFactory::~QItemEditorFactory() +{ +} + +/*! + Registers an item editor creator specified by \a creator for the given \a type of data. + + \bold{Note:} The factory takes ownership of the item editor creator and will destroy + it if a new creator for the same type is registered later. + + \sa createEditor() +*/ +void QItemEditorFactory::registerEditor(QVariant::Type type, QItemEditorCreatorBase *creator) +{ + delete creatorMap.value(type, 0); + creatorMap[type] = creator; +} + +class QDefaultItemEditorFactory : public QItemEditorFactory +{ +public: + inline QDefaultItemEditorFactory() {} + QWidget *createEditor(QVariant::Type type, QWidget *parent) const; + QByteArray valuePropertyName(QVariant::Type) const; +}; + +QWidget *QDefaultItemEditorFactory::createEditor(QVariant::Type type, QWidget *parent) const +{ + switch (type) { +#ifndef QT_NO_COMBOBOX + case QVariant::Bool: { + QBooleanComboBox *cb = new QBooleanComboBox(parent); + cb->setFrame(false); + return cb; } +#endif +#ifndef QT_NO_SPINBOX + case QVariant::UInt: { + QSpinBox *sb = new QSpinBox(parent); + sb->setFrame(false); + sb->setMaximum(INT_MAX); + return sb; } + case QVariant::Int: { + QSpinBox *sb = new QSpinBox(parent); + sb->setFrame(false); + sb->setMinimum(INT_MIN); + sb->setMaximum(INT_MAX); + return sb; } +#endif +#ifndef QT_NO_DATETIMEEDIT + case QVariant::Date: { + QDateTimeEdit *ed = new QDateEdit(parent); + ed->setFrame(false); + return ed; } + case QVariant::Time: { + QDateTimeEdit *ed = new QTimeEdit(parent); + ed->setFrame(false); + return ed; } + case QVariant::DateTime: { + QDateTimeEdit *ed = new QDateTimeEdit(parent); + ed->setFrame(false); + return ed; } +#endif + case QVariant::Pixmap: + return new QLabel(parent); +#ifndef QT_NO_SPINBOX + case QVariant::Double: { + QDoubleSpinBox *sb = new QDoubleSpinBox(parent); + sb->setFrame(false); + sb->setMinimum(-DBL_MAX); + sb->setMaximum(DBL_MAX); + return sb; } +#endif +#ifndef QT_NO_LINEEDIT + case QVariant::String: + default: { + // the default editor is a lineedit + QExpandingLineEdit *le = new QExpandingLineEdit(parent); + le->setFrame(le->style()->styleHint(QStyle::SH_ItemView_DrawDelegateFrame, 0, le)); + if (!le->style()->styleHint(QStyle::SH_ItemView_ShowDecorationSelected, 0, le)) + le->setWidgetOwnsGeometry(true); + return le; } +#else + default: + break; +#endif + } + return 0; +} + +QByteArray QDefaultItemEditorFactory::valuePropertyName(QVariant::Type type) const +{ + switch (type) { +#ifndef QT_NO_COMBOBOX + case QVariant::Bool: + return "currentIndex"; +#endif +#ifndef QT_NO_SPINBOX + case QVariant::UInt: + case QVariant::Int: + case QVariant::Double: + return "value"; +#endif +#ifndef QT_NO_DATETIMEEDIT + case QVariant::Date: + return "date"; + case QVariant::Time: + return "time"; + case QVariant::DateTime: + return "dateTime"; +#endif + case QVariant::String: + default: + // the default editor is a lineedit + return "text"; + } +} + +static QItemEditorFactory *q_default_factory = 0; +struct QDefaultFactoryCleaner +{ + inline QDefaultFactoryCleaner() {} + ~QDefaultFactoryCleaner() { delete q_default_factory; q_default_factory = 0; } +}; + +/*! + Returns the default item editor factory. + + \sa setDefaultFactory() +*/ +const QItemEditorFactory *QItemEditorFactory::defaultFactory() +{ + static const QDefaultItemEditorFactory factory; + if (q_default_factory) + return q_default_factory; + return &factory; +} + +/*! + Sets the default item editor factory to the given \a factory. + Both new and existing delegates will use the new factory. + + \sa defaultFactory() +*/ +void QItemEditorFactory::setDefaultFactory(QItemEditorFactory *factory) +{ + static const QDefaultFactoryCleaner cleaner; + delete q_default_factory; + q_default_factory = factory; +} + +/*! + \class QItemEditorCreatorBase + \brief The QItemEditorCreatorBase class provides an abstract base class that + must be subclassed when implementing new item editor creators. + \since 4.2 + \ingroup model-view + + QItemEditorCreatorBase objects are specialized widget factories that + provide editor widgets for one particular QVariant data type. They + are used by QItemEditorFactory to create editors for + \l{QItemDelegate}s. Creator bases must be registered with + QItemEditorFactory::registerEditor(). + + An editor should provide a user property for the data it edits. + QItemDelagates can then access the property using Qt's + \l{Meta-Object System}{meta-object system} to set and retrieve the + editing data. A property is set as the user property with the USER + keyword: + + \snippet doc/src/snippets/code/src_gui_itemviews_qitemeditorfactory.cpp 0 + + If the editor does not provide a user property, it must return the + name of the property from valuePropertyName(); delegates will then + use the name to access the property. If a user property exists, + item delegates will not call valuePropertyName(). + + QStandardItemEditorCreator is a convenience template class that can be used + to register widgets without the need to subclass QItemEditorCreatorBase. + + \sa QStandardItemEditorCreator, QItemEditorFactory, + {Model/View Programming}, {Color Editor Factory Example} +*/ + +/*! + \fn QItemEditorCreatorBase::~QItemEditorCreatorBase() + + Destroys the editor creator object. +*/ + +/*! + \fn QWidget *QItemEditorCreatorBase::createWidget(QWidget *parent) const + + Returns an editor widget with the given \a parent. + + When implementing this function in subclasses of this class, you must + construct and return new editor widgets with the parent widget specified. +*/ + +/*! + \fn QByteArray QItemEditorCreatorBase::valuePropertyName() const + + Returns the name of the property used to get and set values in the creator's + editor widgets. + + When implementing this function in subclasses, you must ensure that the + editor widget's property specified by this function can accept the type + the creator is registered for. For example, a creator which constructs + QCheckBox widgets to edit boolean values would return the + \l{QCheckBox::checkable}{checkable} property name from this function, + and must be registered in the item editor factory for the QVariant::Bool + type. + + Note: Since Qt 4.2 the item delegates query the user property of widgets, + and only call this function if the widget has no user property. You can + override this behavior by reimplementing QAbstractItemDelegate::setModelData() + and QAbstractItemDelegate::setEditorData(). + + \sa QMetaObject::userProperty(), QItemEditorFactory::registerEditor() +*/ + +/*! + \class QItemEditorCreator + \brief The QItemEditorCreator class makes it possible to create + item editor creator bases without subclassing + QItemEditorCreatorBase. + + \since 4.2 + \ingroup model-view + + QItemEditorCreator is a convenience template class. It uses + the template class to create editors for QItemEditorFactory. + This way, it is not necessary to subclass + QItemEditorCreatorBase. + + \snippet doc/src/snippets/code/src_gui_itemviews_qitemeditorfactory.cpp 1 + + The constructor takes the name of the property that contains the + editing data. QItemDelegate can then access the property by name + when it sets and retrieves editing data. Only use this class if + your editor does not define a user property (using the USER + keyword in the Q_PROPERTY macro). If the widget has a user + property, you should use QStandardItemEditorCreator instead. + + \sa QItemEditorCreatorBase, QStandardItemEditorCreator, + QItemEditorFactory, {Color Editor Factory Example} +*/ + +/*! + \fn QItemEditorCreator::QItemEditorCreator(const QByteArray &valuePropertyName) + + Constructs an editor creator object using \a valuePropertyName + as the name of the property to be used for editing. The + property name is used by QItemDelegate when setting and + getting editor data. + + Note that the \a valuePropertyName is only used if the editor + widget does not have a user property defined. +*/ + +/*! + \fn QWidget *QItemEditorCreator::createWidget(QWidget *parent) const + \reimp +*/ + +/*! + \fn QByteArray QItemEditorCreator::valuePropertyName() const + \reimp +*/ + +/*! + \class QStandardItemEditorCreator + + \brief The QStandardItemEditorCreator class provides the + possibility to register widgets without having to subclass + QItemEditorCreatorBase. + + \since 4.2 + \ingroup model-view + + This convenience template class makes it possible to register widgets without + having to subclass QItemEditorCreatorBase. + + Example: + + \snippet doc/src/snippets/code/src_gui_itemviews_qitemeditorfactory.cpp 2 + + Setting the \c editorFactory created above in an item delegate via + QItemDelegate::setItemEditorFactory() makes sure that all values of type + QVariant::DateTime will be edited in \c{MyFancyDateTimeEdit}. + + The editor must provide a user property that will contain the + editing data. The property is used by \l{QItemDelegate}s to set + and retrieve the data (using Qt's \l{Meta-Object + System}{meta-object system}). You set the user property with + the USER keyword: + + \snippet doc/src/snippets/code/src_gui_itemviews_qitemeditorfactory.cpp 3 + + \sa QItemEditorCreatorBase, QItemEditorCreator, + QItemEditorFactory, QItemDelegate, {Color Editor Factory Example} +*/ + +/*! + \fn QStandardItemEditorCreator::QStandardItemEditorCreator() + + Constructs an editor creator object. +*/ + +/*! + \fn QWidget *QStandardItemEditorCreator::createWidget(QWidget *parent) const + \reimp +*/ + +/*! + \fn QByteArray QStandardItemEditorCreator::valuePropertyName() const + \reimp +*/ + +#ifndef QT_NO_LINEEDIT + +QExpandingLineEdit::QExpandingLineEdit(QWidget *parent) + : QLineEdit(parent), originalWidth(-1), widgetOwnsGeometry(false) +{ + connect(this, SIGNAL(textChanged(QString)), this, SLOT(resizeToContents())); + updateMinimumWidth(); +} + +void QExpandingLineEdit::changeEvent(QEvent *e) +{ + switch (e->type()) + { + case QEvent::FontChange: + case QEvent::StyleChange: + case QEvent::ContentsRectChange: + updateMinimumWidth(); + break; + default: + break; + } + + QLineEdit::changeEvent(e); +} + +void QExpandingLineEdit::updateMinimumWidth() +{ + int left, right; + getTextMargins(&left, 0, &right, 0); + int width = left + right + 4 /*horizontalMargin in qlineedit.cpp*/; + getContentsMargins(&left, 0, &right, 0); + width += left + right; + + QStyleOptionFrameV2 opt; + initStyleOption(&opt); + + int minWidth = style()->sizeFromContents(QStyle::CT_LineEdit, &opt, QSize(width, 0). + expandedTo(QApplication::globalStrut()), this).width(); + setMinimumWidth(minWidth); +} + +void QExpandingLineEdit::resizeToContents() +{ + int oldWidth = width(); + if (originalWidth == -1) + originalWidth = oldWidth; + if (QWidget *parent = parentWidget()) { + QPoint position = pos(); + int hintWidth = minimumWidth() + fontMetrics().width(displayText()); + int parentWidth = parent->width(); + int maxWidth = isRightToLeft() ? position.x() + oldWidth : parentWidth - position.x(); + int newWidth = qBound(originalWidth, hintWidth, maxWidth); + if (widgetOwnsGeometry) + setMaximumWidth(newWidth); + if (isRightToLeft()) + move(position.x() - newWidth + oldWidth, position.y()); + resize(newWidth, height()); + } +} + +#endif // QT_NO_LINEEDIT + +#ifndef QT_NO_COMBOBOX + +QBooleanComboBox::QBooleanComboBox(QWidget *parent) + : QComboBox(parent) +{ + addItem(QComboBox::tr("False")); + addItem(QComboBox::tr("True")); +} + +void QBooleanComboBox::setValue(bool value) +{ + setCurrentIndex(value ? 1 : 0); +} + +bool QBooleanComboBox::value() const +{ + return (currentIndex() == 1); +} + +#endif // QT_NO_COMBOBOX + +QT_END_NAMESPACE + +#if !defined(QT_NO_LINEEDIT) || !defined(QT_NO_COMBOBOX) +#include "qitemeditorfactory.moc" +#endif + +#endif // QT_NO_ITEMVIEWS diff --git a/src/gui/itemviews/qitemeditorfactory.h b/src/gui/itemviews/qitemeditorfactory.h new file mode 100644 index 0000000..46c209a --- /dev/null +++ b/src/gui/itemviews/qitemeditorfactory.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QITEMEDITORFACTORY_H +#define QITEMEDITORFACTORY_H + +#include <QtCore/qmetaobject.h> +#include <QtCore/qbytearray.h> +#include <QtCore/qhash.h> +#include <QtCore/qvariant.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_ITEMVIEWS + +class QWidget; + +class Q_GUI_EXPORT QItemEditorCreatorBase +{ +public: + virtual ~QItemEditorCreatorBase() {} + + virtual QWidget *createWidget(QWidget *parent) const = 0; + virtual QByteArray valuePropertyName() const = 0; +}; + +template <class T> +class QItemEditorCreator : public QItemEditorCreatorBase +{ +public: + inline QItemEditorCreator(const QByteArray &valuePropertyName); + inline QWidget *createWidget(QWidget *parent) const { return new T(parent); } + inline QByteArray valuePropertyName() const { return propertyName; } + +private: + QByteArray propertyName; +}; + +template <class T> +class QStandardItemEditorCreator: public QItemEditorCreatorBase +{ +public: + inline QStandardItemEditorCreator() + : propertyName(T::staticMetaObject.userProperty().name()) + {} + inline QWidget *createWidget(QWidget *parent) const { return new T(parent); } + inline QByteArray valuePropertyName() const { return propertyName; } + +private: + QByteArray propertyName; +}; + + +template <class T> +Q_INLINE_TEMPLATE QItemEditorCreator<T>::QItemEditorCreator(const QByteArray &avaluePropertyName) + : propertyName(avaluePropertyName) {} + +class Q_GUI_EXPORT QItemEditorFactory +{ +public: + inline QItemEditorFactory() {} + virtual ~QItemEditorFactory(); + + virtual QWidget *createEditor(QVariant::Type type, QWidget *parent) const; + virtual QByteArray valuePropertyName(QVariant::Type type) const; + + void registerEditor(QVariant::Type type, QItemEditorCreatorBase *creator); + + static const QItemEditorFactory *defaultFactory(); + static void setDefaultFactory(QItemEditorFactory *factory); + +private: + QHash<QVariant::Type, QItemEditorCreatorBase *> creatorMap; +}; + +#endif // QT_NO_ITEMVIEWS + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QITEMEDITORFACTORY_H diff --git a/src/gui/itemviews/qitemeditorfactory_p.h b/src/gui/itemviews/qitemeditorfactory_p.h new file mode 100644 index 0000000..87f68c5 --- /dev/null +++ b/src/gui/itemviews/qitemeditorfactory_p.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QITEMEDITORFACTORY_P_H +#define QITEMEDITORFACTORY_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + + + +#include <qlineedit.h> + +#ifndef QT_NO_ITEMVIEWS + +#ifndef QT_NO_LINEEDIT + +QT_BEGIN_NAMESPACE + + +class QExpandingLineEdit : public QLineEdit +{ + Q_OBJECT + +public: + QExpandingLineEdit(QWidget *parent); + + void setWidgetOwnsGeometry(bool value) + { + widgetOwnsGeometry = value; + } + +protected: + void changeEvent(QEvent *e); + +public Q_SLOTS: + void resizeToContents(); + +private: + void updateMinimumWidth(); + + int originalWidth; + bool widgetOwnsGeometry; +}; + + +QT_END_NAMESPACE + +#endif // QT_NO_LINEEDIT + +#endif //QT_NO_ITEMVIEWS + +#endif //QITEMEDITORFACTORY_P_H diff --git a/src/gui/itemviews/qitemselectionmodel.cpp b/src/gui/itemviews/qitemselectionmodel.cpp new file mode 100644 index 0000000..1a3ae2d --- /dev/null +++ b/src/gui/itemviews/qitemselectionmodel.cpp @@ -0,0 +1,1570 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qitemselectionmodel.h" +#include <private/qitemselectionmodel_p.h> +#include <qdebug.h> + +#ifndef QT_NO_ITEMVIEWS + +QT_BEGIN_NAMESPACE + +/*! + \class QItemSelectionRange + + \brief The QItemSelectionRange class manages information about a + range of selected items in a model. + + \ingroup model-view + + A QItemSelectionRange contains information about a range of + selected items in a model. A range of items is a contiguous array + of model items, extending to cover a number of adjacent rows and + columns with a common parent item; this can be visualized as a + two-dimensional block of cells in a table. A selection range has a + top(), left() a bottom(), right() and a parent(). + + The QItemSelectionRange class is one of the \l{Model/View Classes} + and is part of Qt's \l{Model/View Programming}{model/view framework}. + + The model items contained in the selection range can be obtained + using the indexes() function. Use QItemSelectionModel::selectedIndexes() + to get a list of all selected items for a view. + + You can determine whether a given model item lies within a + particular range by using the contains() function. Ranges can also + be compared using the overloaded operators for equality and + inequality, and the intersects() function allows you to determine + whether two ranges overlap. + + \sa {Model/View Programming}, QAbstractItemModel, QItemSelection, + QItemSelectionModel +*/ + +/*! + \fn QItemSelectionRange::QItemSelectionRange() + + Constructs an empty selection range. +*/ + +/*! + \fn QItemSelectionRange::QItemSelectionRange(const QItemSelectionRange &other) + + Copy constructor. Constructs a new selection range with the same contents + as the \a other range given. + +*/ + +/*! + \fn QItemSelectionRange::QItemSelectionRange(const QModelIndex &topLeft, const QModelIndex &bottomRight) + + Constructs a new selection range containing only the index specified + by the \a topLeft and the index \a bottomRight. + +*/ + +/*! + \fn QItemSelectionRange::QItemSelectionRange(const QModelIndex &index) + + Constructs a new selection range containing only the model item specified + by the model index \a index. +*/ + +/*! + \fn int QItemSelectionRange::top() const + + Returns the row index corresponding to the uppermost selected row in the + selection range. + +*/ + +/*! + \fn int QItemSelectionRange::left() const + + Returns the column index corresponding to the leftmost selected column in the + selection range. +*/ + +/*! + \fn int QItemSelectionRange::bottom() const + + Returns the row index corresponding to the lowermost selected row in the + selection range. + +*/ + +/*! + \fn int QItemSelectionRange::right() const + + Returns the column index corresponding to the rightmost selected column in + the selection range. + +*/ + +/*! + \fn int QItemSelectionRange::width() const + + Returns the number of selected columns in the selection range. + +*/ + +/*! + \fn int QItemSelectionRange::height() const + + Returns the number of selected rows in the selection range. + +*/ + +/*! + \fn const QAbstractItemModel *QItemSelectionRange::model() const + + Returns the model that the items in the selection range belong to. +*/ + +/*! + \fn QModelIndex QItemSelectionRange::topLeft() const + + Returns the index for the item located at the top-left corner of + the selection range. + + \sa top(), left(), bottomRight() +*/ + +/*! + \fn QModelIndex QItemSelectionRange::bottomRight() const + + Returns the index for the item located at the bottom-right corner + of the selection range. + + \sa bottom(), right(), topLeft() +*/ + +/*! + \fn QModelIndex QItemSelectionRange::parent() const + + Returns the parent model item index of the items in the selection range. + +*/ + +/*! + \fn bool QItemSelectionRange::contains(const QModelIndex &index) const + + Returns true if the model item specified by the \a index lies within the + range of selected items; otherwise returns false. +*/ + +/*! + \fn bool QItemSelectionRange::contains(int row, int column, + const QModelIndex &parentIndex) const + \overload + + Returns true if the model item specified by (\a row, \a column) + and with \a parentIndex as the parent item lies within the range + of selected items; otherwise returns false. +*/ + +/*! + \fn bool QItemSelectionRange::intersects(const QItemSelectionRange &other) const + + Returns true if this selection range intersects (overlaps with) the \a other + range given; otherwise returns false. + +*/ +bool QItemSelectionRange::intersects(const QItemSelectionRange &other) const +{ + return (isValid() && other.isValid() + && parent() == other.parent() + && ((top() <= other.top() && bottom() >= other.top()) + || (top() >= other.top() && top() <= other.bottom())) + && ((left() <= other.left() && right() >= other.left()) + || (left() >= other.left() && left() <= other.right()))); +} + +/*! + \fn QItemSelectionRange QItemSelectionRange::intersect(const QItemSelectionRange &other) const + \obsolete + + Use intersected(\a other) instead. +*/ + +/*! + \fn QItemSelectionRange QItemSelectionRange::intersected(const QItemSelectionRange &other) const + \since 4.2 + + Returns a new selection range containing only the items that are found in + both the selection range and the \a other selection range. +*/ + +QItemSelectionRange QItemSelectionRange::intersect(const QItemSelectionRange &other) const +{ + if (model() == other.model() && parent() == other.parent()) { + QModelIndex topLeft = model()->index(qMax(top(), other.top()), + qMax(left(), other.left()), + other.parent()); + QModelIndex bottomRight = model()->index(qMin(bottom(), other.bottom()), + qMin(right(), other.right()), + other.parent()); + return QItemSelectionRange(topLeft, bottomRight); + } + return QItemSelectionRange(); +} + +/*! + \fn bool QItemSelectionRange::operator==(const QItemSelectionRange &other) const + + Returns true if the selection range is exactly the same as the \a other + range given; otherwise returns false. + +*/ + +/*! + \fn bool QItemSelectionRange::operator!=(const QItemSelectionRange &other) const + + Returns true if the selection range differs from the \a other range given; + otherwise returns false. + +*/ + +/*! + \fn bool QItemSelectionRange::isValid() const + + Returns true if the selection range is valid; otherwise returns false. + +*/ + +/*! + Returns the list of model index items stored in the selection. +*/ + +QModelIndexList QItemSelectionRange::indexes() const +{ + QModelIndex index; + QModelIndexList result; + if (isValid() && model()) { + for (int column = left(); column <= right(); ++column) { + for (int row = top(); row <= bottom(); ++row) { + index = model()->index(row, column, parent()); + Qt::ItemFlags flags = model()->flags(index); + if ((flags & Qt::ItemIsSelectable) && (flags & Qt::ItemIsEnabled)) + result.append(index); + } + } + } + return result; +} + +/*! + \class QItemSelection + + \brief The QItemSelection class manages information about selected items in a model. + + \ingroup model-view + + A QItemSelection describes the items in a model that have been + selected by the user. A QItemSelection is basically a list of + selection ranges, see QItemSelectionRange. It provides functions for + creating and manipulating selections, and selecting a range of items + from a model. + + The QItemSelection class is one of the \l{Model/View Classes} + and is part of Qt's \l{Model/View Programming}{model/view framework}. + + An item selection can be constructed and initialized to contain a + range of items from an existing model. The following example constructs + a selection that contains a range of items from the given \c model, + beginning at the \c topLeft, and ending at the \c bottomRight. + + \snippet doc/src/snippets/code/src_gui_itemviews_qitemselectionmodel.cpp 0 + + An empty item selection can be constructed, and later populated as + required. So, if the model is going to be unavailable when we construct + the item selection, we can rewrite the above code in the following way: + + \snippet doc/src/snippets/code/src_gui_itemviews_qitemselectionmodel.cpp 1 + + QItemSelection saves memory, and avoids unnecessary work, by working with + selection ranges rather than recording the model item index for each + item in the selection. Generally, an instance of this class will contain + a list of non-overlapping selection ranges. + + Use merge() to merge one item selection into another without making + overlapping ranges. Use split() to split one selection range into + smaller ranges based on a another selection range. + + \sa {Model/View Programming}, QItemSelectionModel + +*/ + +/*! + \fn QItemSelection::QItemSelection() + + Constructs an empty selection. +*/ + +/*! + Constructs an item selection that extends from the top-left model item, + specified by the \a topLeft index, to the bottom-right item, specified + by \a bottomRight. +*/ +QItemSelection::QItemSelection(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + select(topLeft, bottomRight); +} + +/*! + Adds the items in the range that extends from the top-left model + item, specified by the \a topLeft index, to the bottom-right item, + specified by \a bottomRight to the list. + + \note \a topLeft and \a bottomRight must have the same parent. +*/ +void QItemSelection::select(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + if (!topLeft.isValid() || !bottomRight.isValid()) + return; + + if ((topLeft.model() != bottomRight.model()) + || topLeft.parent() != bottomRight.parent()) { + qWarning("Can't select indexes from different model or with different parents"); + return; + } + if (topLeft.row() > bottomRight.row() || topLeft.column() > bottomRight.column()) { + int top = qMin(topLeft.row(), bottomRight.row()); + int bottom = qMax(topLeft.row(), bottomRight.row()); + int left = qMin(topLeft.column(), bottomRight.column()); + int right = qMax(topLeft.column(), bottomRight.column()); + QModelIndex tl = topLeft.sibling(top, left); + QModelIndex br = bottomRight.sibling(bottom, right); + append(QItemSelectionRange(tl, br)); + return; + } + append(QItemSelectionRange(topLeft, bottomRight)); +} + +/*! + Returns true if the selection contains the given \a index; otherwise + returns false. +*/ + +bool QItemSelection::contains(const QModelIndex &index) const +{ + if (index.flags() & Qt::ItemIsSelectable) { + QList<QItemSelectionRange>::const_iterator it = begin(); + for (; it != end(); ++it) + if ((*it).contains(index)) + return true; + } + return false; +} + +/*! + Returns a list of model indexes that correspond to the selected items. +*/ + +QModelIndexList QItemSelection::indexes() const +{ + QModelIndexList result; + QList<QItemSelectionRange>::const_iterator it = begin(); + for (; it != end(); ++it) + result += (*it).indexes(); + return result; +} + +/*! + Merges the \a other selection with this QItemSelection using the + \a command given. This method guarantees that no ranges are overlapping. + + Note that only QItemSelectionModel::Select, + QItemSelectionModel::Deselect, and QItemSelectionModel::Toggle are + supported. + + \sa split() +*/ +void QItemSelection::merge(const QItemSelection &other, QItemSelectionModel::SelectionFlags command) +{ + if (other.isEmpty() || + !(command & QItemSelectionModel::Select || + command & QItemSelectionModel::Deselect || + command & QItemSelectionModel::Toggle)) + return; + + QItemSelection newSelection = other; + // Collect intersections + QItemSelection intersections; + QItemSelection::iterator it = newSelection.begin(); + while (it != newSelection.end()) { + if (!(*it).isValid()) { + it = newSelection.erase(it); + continue; + } + for (int t = 0; t < count(); ++t) { + if ((*it).intersects(at(t))) + intersections.append(at(t).intersected(*it)); + } + ++it; + } + + // Split the old (and new) ranges using the intersections + for (int i = 0; i < intersections.count(); ++i) { // for each intersection + for (int t = 0; t < count();) { // splitt each old range + if (at(t).intersects(intersections.at(i))) { + split(at(t), intersections.at(i), this); + removeAt(t); + } else { + ++t; + } + } + // only split newSelection if Toggle is specified + for (int n = 0; (command & QItemSelectionModel::Toggle) && n < newSelection.count();) { + if (newSelection.at(n).intersects(intersections.at(i))) { + split(newSelection.at(n), intersections.at(i), &newSelection); + newSelection.removeAt(n); + } else { + ++n; + } + } + } + // do not add newSelection for Deselect + if (!(command & QItemSelectionModel::Deselect)) + operator+=(newSelection); +} + +/*! + Splits the selection \a range using the selection \a other range. + Removes all items in \a other from \a range and puts the result in \a result. + This can be compared with the semantics of the \e subtract operation of a set. + \sa merge() +*/ + +void QItemSelection::split(const QItemSelectionRange &range, + const QItemSelectionRange &other, QItemSelection *result) +{ + if (range.parent() != other.parent()) + return; + + QModelIndex parent = other.parent(); + int top = range.top(); + int left = range.left(); + int bottom = range.bottom(); + int right = range.right(); + int other_top = other.top(); + int other_left = other.left(); + int other_bottom = other.bottom(); + int other_right = other.right(); + const QAbstractItemModel *model = range.model(); + Q_ASSERT(model); + if (other_top > top) { + QModelIndex tl = model->index(top, left, parent); + QModelIndex br = model->index(other_top - 1, right, parent); + result->append(QItemSelectionRange(tl, br)); + top = other_top; + } + if (other_bottom < bottom) { + QModelIndex tl = model->index(other_bottom + 1, left, parent); + QModelIndex br = model->index(bottom, right, parent); + result->append(QItemSelectionRange(tl, br)); + bottom = other_bottom; + } + if (other_left > left) { + QModelIndex tl = model->index(top, left, parent); + QModelIndex br = model->index(bottom, other_left - 1, parent); + result->append(QItemSelectionRange(tl, br)); + left = other_left; + } + if (other_right < right) { + QModelIndex tl = model->index(top, other_right + 1, parent); + QModelIndex br = model->index(bottom, right, parent); + result->append(QItemSelectionRange(tl, br)); + right = other_right; + } +} + +/*! + \internal + + returns a QItemSelection where all ranges have been expanded to: + Rows: left: 0 and right: columnCount()-1 + Columns: top: 0 and bottom: rowCount()-1 +*/ + +QItemSelection QItemSelectionModelPrivate::expandSelection(const QItemSelection &selection, + QItemSelectionModel::SelectionFlags command) const +{ + if (selection.isEmpty() && !((command & QItemSelectionModel::Rows) || + (command & QItemSelectionModel::Columns))) + return selection; + + QItemSelection expanded; + if (command & QItemSelectionModel::Rows) { + for (int i = 0; i < selection.count(); ++i) { + QModelIndex parent = selection.at(i).parent(); + int colCount = model->columnCount(parent); + QModelIndex tl = model->index(selection.at(i).top(), 0, parent); + QModelIndex br = model->index(selection.at(i).bottom(), colCount - 1, parent); + //we need to merge because the same row could have already been inserted + expanded.merge(QItemSelection(tl, br), QItemSelectionModel::Select); + } + } + if (command & QItemSelectionModel::Columns) { + for (int i = 0; i < selection.count(); ++i) { + QModelIndex parent = selection.at(i).parent(); + int rowCount = model->rowCount(parent); + QModelIndex tl = model->index(0, selection.at(i).left(), parent); + QModelIndex br = model->index(rowCount - 1, selection.at(i).right(), parent); + //we need to merge because the same column could have already been inserted + expanded.merge(QItemSelection(tl, br), QItemSelectionModel::Select); + } + } + return expanded; +} + +/*! + \internal +*/ +void QItemSelectionModelPrivate::_q_rowsAboutToBeRemoved(const QModelIndex &parent, + int start, int end) +{ + Q_Q(QItemSelectionModel); + + // update current index + if (currentIndex.isValid() && parent == currentIndex.parent() + && currentIndex.row() >= start && currentIndex.row() <= end) { + QModelIndex old = currentIndex; + if (start > 0) // there are rows left above the change + currentIndex = model->index(start - 1, old.column(), parent); + else if (model && end < model->rowCount(parent) - 1) // there are rows left below the change + currentIndex = model->index(end + 1, old.column(), parent); + else // there are no rows left in the table + currentIndex = QModelIndex(); + emit q->currentChanged(currentIndex, old); + emit q->currentRowChanged(currentIndex, old); + if (currentIndex.column() != old.column()) + emit q->currentColumnChanged(currentIndex, old); + } + + // update selectionsx + QModelIndex tl = model->index(start, 0, parent); + QModelIndex br = model->index(end, model->columnCount(parent) - 1, parent); + q->select(QItemSelection(tl, br), QItemSelectionModel::Deselect); + finalize(); +} + +/*! + \internal +*/ +void QItemSelectionModelPrivate::_q_columnsAboutToBeRemoved(const QModelIndex &parent, + int start, int end) +{ + Q_Q(QItemSelectionModel); + + // update current index + if (currentIndex.isValid() && parent == currentIndex.parent() + && currentIndex.column() >= start && currentIndex.column() <= end) { + QModelIndex old = currentIndex; + if (start > 0) // there are columns to the left of the change + currentIndex = model->index(old.row(), start - 1, parent); + else if (model && end < model->columnCount() - 1) // there are columns to the right of the change + currentIndex = model->index(old.row(), end + 1, parent); + else // there are no columns left in the table + currentIndex = QModelIndex(); + emit q->currentChanged(currentIndex, old); + if (currentIndex.row() != old.row()) + emit q->currentRowChanged(currentIndex, old); + emit q->currentColumnChanged(currentIndex, old); + } + + // update selections + QModelIndex tl = model->index(0, start, parent); + QModelIndex br = model->index(model->rowCount(parent) - 1, end, parent); + q->select(QItemSelection(tl, br), QItemSelectionModel::Deselect); + finalize(); +} + +/*! + \internal + + Split selection ranges if columns are about to be inserted in the middle. +*/ +void QItemSelectionModelPrivate::_q_columnsAboutToBeInserted(const QModelIndex &parent, + int start, int end) +{ + Q_UNUSED(end); + finalize(); + QList<QItemSelectionRange> split; + QList<QItemSelectionRange>::iterator it = ranges.begin(); + for (; it != ranges.end(); ) { + if ((*it).isValid() && (*it).parent() == parent + && (*it).left() < start && (*it).right() >= start) { + QModelIndex bottomMiddle = model->index((*it).bottom(), start - 1, (*it).parent()); + QItemSelectionRange left((*it).topLeft(), bottomMiddle); + QModelIndex topMiddle = model->index((*it).top(), start, (*it).parent()); + QItemSelectionRange right(topMiddle, (*it).bottomRight()); + it = ranges.erase(it); + split.append(left); + split.append(right); + } else { + ++it; + } + } + ranges += split; +} + +/*! + \internal + + Split selection ranges if rows are about to be inserted in the middle. +*/ +void QItemSelectionModelPrivate::_q_rowsAboutToBeInserted(const QModelIndex &parent, + int start, int end) +{ + Q_UNUSED(end); + finalize(); + QList<QItemSelectionRange> split; + QList<QItemSelectionRange>::iterator it = ranges.begin(); + for (; it != ranges.end(); ) { + if ((*it).isValid() && (*it).parent() == parent + && (*it).top() < start && (*it).bottom() >= start) { + QModelIndex middleRight = model->index(start - 1, (*it).right(), (*it).parent()); + QItemSelectionRange top((*it).topLeft(), middleRight); + QModelIndex middleLeft = model->index(start, (*it).left(), (*it).parent()); + QItemSelectionRange bottom(middleLeft, (*it).bottomRight()); + it = ranges.erase(it); + split.append(top); + split.append(bottom); + } else { + ++it; + } + } + ranges += split; +} + +/*! + \internal + + Split selection into individual (persistent) indexes. This is done in + preparation for the layoutChanged() signal, where the indexes can be + merged again. +*/ +void QItemSelectionModelPrivate::_q_layoutAboutToBeChanged() +{ + savedPersistentIndexes.clear(); + savedPersistentCurrentIndexes.clear(); + + // special case for when all indexes are selected + if (ranges.isEmpty() && currentSelection.count() == 1) { + QItemSelectionRange range = currentSelection.first(); + QModelIndex parent = range.parent(); + if (range.top() == 0 + && range.left() == 0 + && range.bottom() == model->rowCount(parent) - 1 + && range.right() == model->columnCount(parent) - 1) { + tableSelected = true; + tableParent = parent; + tableColCount = model->columnCount(parent); + tableRowCount = model->rowCount(parent); + return; + } + } + tableSelected = false; + + QModelIndexList indexes = ranges.indexes(); + QModelIndexList::const_iterator it; + for (it = indexes.constBegin(); it != indexes.constEnd(); ++it) + savedPersistentIndexes.append(QPersistentModelIndex(*it)); + indexes = currentSelection.indexes(); + for (it = indexes.constBegin(); it != indexes.constEnd(); ++it) + savedPersistentCurrentIndexes.append(QPersistentModelIndex(*it)); +} + +/*! + \internal + + Merges \a indexes into an item selection made up of ranges. + Assumes that the indexes are sorted. +*/ +static QItemSelection mergeIndexes(const QList<QPersistentModelIndex> &indexes) +{ + QItemSelection colSpans; + // merge columns + int i = 0; + while (i < indexes.count()) { + QModelIndex tl = indexes.at(i); + QModelIndex br = tl; + while (++i < indexes.count()) { + QModelIndex next = indexes.at(i); + if ((next.parent() == br.parent()) + && (next.row() == br.row()) + && (next.column() == br.column() + 1)) + br = next; + else + break; + } + colSpans.append(QItemSelectionRange(tl, br)); + } + // merge rows + QItemSelection rowSpans; + i = 0; + while (i < colSpans.count()) { + QModelIndex tl = colSpans.at(i).topLeft(); + QModelIndex br = colSpans.at(i).bottomRight(); + QModelIndex prevTl = tl; + while (++i < colSpans.count()) { + QModelIndex nextTl = colSpans.at(i).topLeft(); + QModelIndex nextBr = colSpans.at(i).bottomRight(); + if ((nextTl.column() == prevTl.column()) && (nextBr.column() == br.column()) + && (nextTl.row() == prevTl.row() + 1) && (nextBr.row() == br.row() + 1)) { + br = nextBr; + prevTl = nextTl; + } else { + break; + } + } + rowSpans.append(QItemSelectionRange(tl, br)); + } + return rowSpans; +} + +/*! + \internal + + Merge the selected indexes into selection ranges again. +*/ +void QItemSelectionModelPrivate::_q_layoutChanged() +{ + // special case for when all indexes are selected + if (tableSelected && tableColCount == model->columnCount(tableParent) + && tableRowCount == model->rowCount(tableParent)) { + ranges.clear(); + currentSelection.clear(); + int bottom = tableRowCount - 1; + int right = tableColCount - 1; + QModelIndex tl = model->index(0, 0, tableParent); + QModelIndex br = model->index(bottom, right, tableParent); + currentSelection << QItemSelectionRange(tl, br); + tableParent = QModelIndex(); + tableSelected = false; + return; + } + + if (savedPersistentCurrentIndexes.isEmpty() && savedPersistentIndexes.isEmpty()) { + // either the selection was actually empty, or we + // didn't get the layoutAboutToBeChanged() signal + return; + } + // clear the "old" selection + ranges.clear(); + currentSelection.clear(); + + // sort the "new" selection, as preparation for merging + qStableSort(savedPersistentIndexes.begin(), savedPersistentIndexes.end()); + qStableSort(savedPersistentCurrentIndexes.begin(), savedPersistentCurrentIndexes.end()); + + // update the selection by merging the individual indexes + ranges = mergeIndexes(savedPersistentIndexes); + currentSelection = mergeIndexes(savedPersistentCurrentIndexes); + + // release the persistent indexes + savedPersistentIndexes.clear(); + savedPersistentCurrentIndexes.clear(); +} + +/*! + \class QItemSelectionModel + + \brief The QItemSelectionModel class keeps track of a view's selected items. + + \ingroup model-view + + A QItemSelectionModel keeps track of the selected items in a view, or + in several views onto the same model. It also keeps track of the + currently selected item in a view. + + The QItemSelectionModel class is one of the \l{Model/View Classes} + and is part of Qt's \l{Model/View Programming}{model/view framework}. + + The selected items are stored using ranges. Whenever you want to + modify the selected items use select() and provide either a + QItemSelection, or a QModelIndex and a QItemSelectionModel::SelectionFlag. + + The QItemSelectionModel takes a two layer approach to selection + management, dealing with both selected items that have been committed + and items that are part of the current selection. The current + selected items are part of the current interactive selection (for + example with rubber-band selection or keyboard-shift selections). + + To update the currently selected items, use the bitwise OR of + QItemSelectionModel::Current and any of the other SelectionFlags. + If you omit the QItemSelectionModel::Current command, a new current + selection will be created, and the previous one added to the whole + selection. All functions operate on both layers; for example, + selectedItems() will return items from both layers. + + \sa {Model/View Programming}, QAbstractItemModel, {Chart Example} +*/ + +/*! + Constructs a selection model that operates on the specified item \a model. +*/ +QItemSelectionModel::QItemSelectionModel(QAbstractItemModel *model) + : QObject(*new QItemSelectionModelPrivate, model) +{ + d_func()->model = model; + if (model) { + connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int))); + connect(model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_columnsAboutToBeRemoved(QModelIndex,int,int))); + connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_rowsAboutToBeInserted(QModelIndex,int,int))); + connect(model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_columnsAboutToBeInserted(QModelIndex,int,int))); + connect(model, SIGNAL(layoutAboutToBeChanged()), + this, SLOT(_q_layoutAboutToBeChanged())); + connect(model, SIGNAL(layoutChanged()), + this, SLOT(_q_layoutChanged())); + } +} + +/*! + Constructs a selection model that operates on the specified item \a model with \a parent. +*/ +QItemSelectionModel::QItemSelectionModel(QAbstractItemModel *model, QObject *parent) + : QObject(*new QItemSelectionModelPrivate, parent) +{ + d_func()->model = model; + if (model) { + connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int))); + connect(model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_columnsAboutToBeRemoved(QModelIndex,int,int))); + connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_rowsAboutToBeInserted(QModelIndex,int,int))); + connect(model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_columnsAboutToBeInserted(QModelIndex,int,int))); + connect(model, SIGNAL(layoutAboutToBeChanged()), + this, SLOT(_q_layoutAboutToBeChanged())); + connect(model, SIGNAL(layoutChanged()), + this, SLOT(_q_layoutChanged())); + } +} + +/*! + \internal +*/ +QItemSelectionModel::QItemSelectionModel(QItemSelectionModelPrivate &dd, QAbstractItemModel *model) + : QObject(dd, model) +{ + d_func()->model = model; + if (model) { + connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int))); + connect(model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_columnsAboutToBeRemoved(QModelIndex,int,int))); + connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_rowsAboutToBeInserted(QModelIndex,int,int))); + connect(model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_columnsAboutToBeInserted(QModelIndex,int,int))); + connect(model, SIGNAL(layoutAboutToBeChanged()), + this, SLOT(_q_layoutAboutToBeChanged())); + connect(model, SIGNAL(layoutChanged()), + this, SLOT(_q_layoutChanged())); + } +} + +/*! + Destroys the selection model. +*/ +QItemSelectionModel::~QItemSelectionModel() +{ + Q_D(QItemSelectionModel); + if (d->model) { + disconnect(d->model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_columnsAboutToBeRemoved(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_rowsAboutToBeInserted(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_columnsAboutToBeInserted(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(layoutAboutToBeChanged()), + this, SLOT(_q_layoutAboutToBeChanged())); + disconnect(d->model, SIGNAL(layoutChanged()), + this, SLOT(_q_layoutChanged())); + } +} + +/*! + Selects the model item \a index using the specified \a command, and emits + selectionChanged(). + + \sa QItemSelectionModel::SelectionFlags +*/ +void QItemSelectionModel::select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command) +{ + QItemSelection selection(index, index); + select(selection, command); +} + +/*! + \fn void QItemSelectionModel::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) + + This signal is emitted whenever the current item changes. The \a previous + model item index is replaced by the \a current index as the selection's + current item. + + Note that this signal will not be emitted when the item model is reset. + + \sa currentIndex() setCurrentIndex() selectionChanged() +*/ + +/*! + \fn void QItemSelectionModel::currentColumnChanged(const QModelIndex ¤t, const QModelIndex &previous) + + This signal is emitted if the \a current item changes and its column is + different to the column of the \a previous current item. + + Note that this signal will not be emitted when the item model is reset. + + \sa currentChanged() currentRowChanged() currentIndex() setCurrentIndex() +*/ + +/*! + \fn void QItemSelectionModel::currentRowChanged(const QModelIndex ¤t, const QModelIndex &previous) + + This signal is emitted if the \a current item changes and its row is + different to the row of the \a previous current item. + + Note that this signal will not be emitted when the item model is reset. + + \sa currentChanged() currentColumnChanged() currentIndex() setCurrentIndex() +*/ + +/*! + \fn void QItemSelectionModel::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) + + This signal is emitted whenever the selection changes. The change in the + selection is represented as an item selection of \a deselected items and + an item selection of \a selected items. + + Note the that the current index changes independently from the selection. + Also note that this signal will not be emitted when the item model is reset. + + \sa select() currentChanged() +*/ + +/*! + \enum QItemSelectionModel::SelectionFlag + + This enum describes the way the selection model will be updated. + + \value NoUpdate No selection will be made. + \value Clear The complete selection will be cleared. + \value Select All specified indexes will be selected. + \value Deselect All specified indexes will be deselected. + \value Toggle All specified indexes will be selected or + deselected depending on their current state. + \value Current The current selection will be updated. + \value Rows All indexes will be expanded to span rows. + \value Columns All indexes will be expanded to span columns. + \value SelectCurrent A combination of Select and Current, provided for + convenience. + \value ToggleCurrent A combination of Toggle and Current, provided for + convenience. + \value ClearAndSelect A combination of Clear and Select, provided for + convenience. +*/ + +/*! + Selects the item \a selection using the specified \a command, and emits + selectionChanged(). + + \sa QItemSelectionModel::SelectionFlag +*/ +void QItemSelectionModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) +{ + Q_D(QItemSelectionModel); + if (command == NoUpdate) + return; + + // store old selection + QItemSelection sel = selection; + QItemSelection old = d->ranges; + old.merge(d->currentSelection, d->currentCommand); + + // expand selection according to SelectionBehavior + if (command & Rows || command & Columns) + sel = d->expandSelection(sel, command); + + // clear ranges and currentSelection + if (command & Clear) { + d->ranges.clear(); + d->currentSelection.clear(); + } + + // merge and clear currentSelection if Current was not set (ie. start new currentSelection) + if (!(command & Current)) + d->finalize(); + + // update currentSelection + if (command & Toggle || command & Select || command & Deselect) { + d->currentCommand = command; + d->currentSelection = sel; + } + + // generate new selection, compare with old and emit selectionChanged() + QItemSelection newSelection = d->ranges; + newSelection.merge(d->currentSelection, d->currentCommand); + emitSelectionChanged(newSelection, old); +} + +/*! + Clears the selection model. Emits selectionChanged() and currentChanged(). +*/ +void QItemSelectionModel::clear() +{ + Q_D(QItemSelectionModel); + clearSelection(); + QModelIndex previous = d->currentIndex; + d->currentIndex = QModelIndex(); + if (previous.isValid()) { + emit currentChanged(d->currentIndex, previous); + emit currentRowChanged(d->currentIndex, previous); + emit currentColumnChanged(d->currentIndex, previous); + } +} + +/*! + Clears the selection model. Does not emit any signals. +*/ +void QItemSelectionModel::reset() +{ + bool block = blockSignals(true); + clear(); + blockSignals(block); +} + +/*! + \since 4.2 + Clears the selection in the selection model. Emits selectionChanged(). +*/ +void QItemSelectionModel::clearSelection() +{ + Q_D(QItemSelectionModel); + if (d->ranges.count() == 0 && d->currentSelection.count() == 0) + return; + QItemSelection selection = d->ranges; + selection.merge(d->currentSelection, d->currentCommand); + d->ranges.clear(); + d->currentSelection.clear(); + emit selectionChanged(QItemSelection(), selection); +} + + +/*! + Sets the model item \a index to be the current item, and emits + currentChanged(). The current item is used for keyboard navigation and + focus indication; it is independent of any selected items, although a + selected item can also be the current item. + + Depending on the specified \a command, the \a index can also become part + of the current selection. + \sa select() +*/ +void QItemSelectionModel::setCurrentIndex(const QModelIndex &index, QItemSelectionModel::SelectionFlags command) +{ + Q_D(QItemSelectionModel); + if (index == d->currentIndex) { + if (command != NoUpdate) + select(index, command); // select item + return; + } + QPersistentModelIndex previous = d->currentIndex; + d->currentIndex = index; // set current before emitting selection changed below + if (command != NoUpdate) + select(d->currentIndex, command); // select item + emit currentChanged(d->currentIndex, previous); + if (d->currentIndex.row() != previous.row() || + d->currentIndex.parent() != previous.parent()) + emit currentRowChanged(d->currentIndex, previous); + if (d->currentIndex.column() != previous.column() || + d->currentIndex.parent() != previous.parent()) + emit currentColumnChanged(d->currentIndex, previous); +} + +/*! + Returns the model item index for the current item, or an invalid index + if there is no current item. +*/ +QModelIndex QItemSelectionModel::currentIndex() const +{ + return static_cast<QModelIndex>(d_func()->currentIndex); +} + +/*! + Returns true if the given model item \a index is selected. +*/ +bool QItemSelectionModel::isSelected(const QModelIndex &index) const +{ + Q_D(const QItemSelectionModel); + if (d->model != index.model() || !index.isValid()) + return false; + + bool selected = false; + // search model ranges + QList<QItemSelectionRange>::const_iterator it = d->ranges.begin(); + for (; it != d->ranges.end(); ++it) { + if ((*it).isValid() && (*it).contains(index)) { + selected = true; + break; + } + } + + // check currentSelection + if (d->currentSelection.count()) { + if ((d->currentCommand & Deselect) && selected) + selected = !d->currentSelection.contains(index); + else if (d->currentCommand & Toggle) + selected ^= d->currentSelection.contains(index); + else if ((d->currentCommand & Select) && !selected) + selected = d->currentSelection.contains(index); + } + + if (selected) { + Qt::ItemFlags flags = d->model->flags(index); + return (flags & Qt::ItemIsSelectable); + } + + return false; +} + +/*! + Returns true if all items are selected in the \a row with the given + \a parent. + + Note that this function is usually faster than calling isSelected() + on all items in the same row and that unselectable items are + ignored. +*/ +bool QItemSelectionModel::isRowSelected(int row, const QModelIndex &parent) const +{ + Q_D(const QItemSelectionModel); + if (parent.isValid() && d->model != parent.model()) + return false; + + // return false if row exist in currentSelection (Deselect) + if (d->currentCommand & Deselect && d->currentSelection.count()) { + for (int i=0; i<d->currentSelection.count(); ++i) { + if (d->currentSelection.at(i).parent() == parent && + row >= d->currentSelection.at(i).top() && + row <= d->currentSelection.at(i).bottom()) + return false; + } + } + // return false if ranges in both currentSelection and ranges + // intersect and have the same row contained + if (d->currentCommand & Toggle && d->currentSelection.count()) { + for (int i=0; i<d->currentSelection.count(); ++i) + if (d->currentSelection.at(i).top() <= row && + d->currentSelection.at(i).bottom() >= row) + for (int j=0; j<d->ranges.count(); ++j) + if (d->ranges.at(j).top() <= row && d->ranges.at(j).bottom() >= row + && d->currentSelection.at(i).intersected(d->ranges.at(j)).isValid()) + return false; + } + // add ranges and currentSelection and check through them all + QList<QItemSelectionRange>::const_iterator it; + QList<QItemSelectionRange> joined = d->ranges; + if (d->currentSelection.count()) + joined += d->currentSelection; + int colCount = d->model->columnCount(parent); + for (int column = 0; column < colCount; ++column) { + for (it = joined.constBegin(); it != joined.constEnd(); ++it) { + if ((*it).contains(row, column, parent)) { + bool selectable = false; + for (int i = column; !selectable && i <= (*it).right(); ++i) { + Qt::ItemFlags flags = d->model->index(row, i, parent).flags(); + selectable = flags & Qt::ItemIsSelectable; + } + if (selectable){ + column = qMax(column, (*it).right()); + break; + } + } + } + if (it == joined.constEnd()) + return false; + } + return colCount > 0; // no columns means no selected items +} + +/*! + Returns true if all items are selected in the \a column with the given + \a parent. + + Note that this function is usually faster than calling isSelected() + on all items in the same column and that unselectable items are + ignored. +*/ +bool QItemSelectionModel::isColumnSelected(int column, const QModelIndex &parent) const +{ + Q_D(const QItemSelectionModel); + if (parent.isValid() && d->model != parent.model()) + return false; + + // return false if column exist in currentSelection (Deselect) + if (d->currentCommand & Deselect && d->currentSelection.count()) { + for (int i = 0; i < d->currentSelection.count(); ++i) { + if (d->currentSelection.at(i).parent() == parent && + column >= d->currentSelection.at(i).left() && + column <= d->currentSelection.at(i).right()) + return false; + } + } + // return false if ranges in both currentSelection and the selection model + // intersect and have the same column contained + if (d->currentCommand & Toggle && d->currentSelection.count()) { + for (int i = 0; i < d->currentSelection.count(); ++i) { + if (d->currentSelection.at(i).left() <= column && + d->currentSelection.at(i).right() >= column) { + for (int j = 0; j < d->ranges.count(); ++j) { + if (d->ranges.at(j).left() <= column && d->ranges.at(j).right() >= column + && d->currentSelection.at(i).intersected(d->ranges.at(j)).isValid()) { + return false; + } + } + } + } + } + // add ranges and currentSelection and check through them all + QList<QItemSelectionRange>::const_iterator it; + QList<QItemSelectionRange> joined = d->ranges; + if (d->currentSelection.count()) + joined += d->currentSelection; + int rowCount = d->model->rowCount(parent); + for (int row = 0; row < rowCount; ++row) { + for (it = joined.constBegin(); it != joined.constEnd(); ++it) { + if ((*it).contains(row, column, parent)) { + Qt::ItemFlags flags = d->model->index(row, column, parent).flags(); + if ((flags & Qt::ItemIsSelectable) && (flags & Qt::ItemIsEnabled)) { + row = qMax(row, (*it).bottom()); + break; + } + } + } + if (it == joined.constEnd()) + return false; + } + return rowCount > 0; // no rows means no selected items +} + +/*! + Returns true if there are any items selected in the \a row with the given + \a parent. +*/ +bool QItemSelectionModel::rowIntersectsSelection(int row, const QModelIndex &parent) const +{ + Q_D(const QItemSelectionModel); + if (parent.isValid() && d->model != parent.model()) + return false; + + QItemSelection sel = d->ranges; + sel.merge(d->currentSelection, d->currentCommand); + for (int i = 0; i < sel.count(); ++i) { + int top = sel.at(i).top(); + int bottom = sel.at(i).bottom(); + int left = sel.at(i).left(); + int right = sel.at(i).right(); + if (top <= row && bottom >= row) { + Qt::ItemFlags leftFlags = d->model->index(row, left, parent).flags(); + Qt::ItemFlags rightFlags = d->model->index(row, right, parent).flags(); + if ((leftFlags & Qt::ItemIsSelectable) && (leftFlags & Qt::ItemIsEnabled) + && (rightFlags & Qt::ItemIsSelectable) && (rightFlags & Qt::ItemIsEnabled)) + return true; + } + } + + return false; +} + +/*! + Returns true if there are any items selected in the \a column with the given + \a parent. +*/ +bool QItemSelectionModel::columnIntersectsSelection(int column, const QModelIndex &parent) const +{ + Q_D(const QItemSelectionModel); + if (parent.isValid() && d->model != parent.model()) + return false; + + QItemSelection sel = d->ranges; + sel.merge(d->currentSelection, d->currentCommand); + for (int i = 0; i < sel.count(); ++i) { + int left = sel.at(i).left(); + int right = sel.at(i).right(); + int top = sel.at(i).top(); + int bottom = sel.at(i).bottom(); + if (left <= column && right >= column) { + Qt::ItemFlags topFlags = d->model->index(top, column, parent).flags(); + Qt::ItemFlags bottomFlags = d->model->index(bottom, column, parent).flags(); + if ((topFlags & Qt::ItemIsSelectable) && (topFlags & Qt::ItemIsEnabled) + && (bottomFlags & Qt::ItemIsSelectable) && (bottomFlags & Qt::ItemIsEnabled)) + return true; + } + } + + return false; +} + +/*! + \since 4.2 + + Returns true if the selection model contains any selection ranges; + otherwise returns false. +*/ +bool QItemSelectionModel::hasSelection() const +{ + Q_D(const QItemSelectionModel); + if (d->currentCommand & (Toggle | Deselect)) { + QItemSelection sel = d->ranges; + sel.merge(d->currentSelection, d->currentCommand); + return !sel.isEmpty(); + } + else { + return !(d->ranges.isEmpty() && d->currentSelection.isEmpty()); + } +} + +/*! + Returns a list of all selected model item indexes. The list contains no + duplicates, and is not sorted. +*/ +QModelIndexList QItemSelectionModel::selectedIndexes() const +{ + Q_D(const QItemSelectionModel); + QItemSelection selected = d->ranges; + selected.merge(d->currentSelection, d->currentCommand); + return selected.indexes(); +} + +/*! + \since 4.2 + Returns the indexes in the given \a column for the rows where all columns are selected. + + \sa selectedIndexes(), selectedColumns() +*/ + +QModelIndexList QItemSelectionModel::selectedRows(int column) const +{ + QModelIndexList indexes; + //the QSet contains pairs of parent modelIndex + //and row number + QSet< QPair<QModelIndex, int> > rowsSeen; + + const QItemSelection ranges = selection(); + for (int i = 0; i < ranges.count(); ++i) { + const QItemSelectionRange &range = ranges.at(i); + QModelIndex parent = range.parent(); + for (int row = range.top(); row <= range.bottom(); row++) { + QPair<QModelIndex, int> rowDef = qMakePair(parent, row); + if (!rowsSeen.contains(rowDef)) { + rowsSeen << rowDef; + if (isRowSelected(row, parent)) { + indexes.append(model()->index(row, column, parent)); + } + } + } + } + + return indexes; +} + +/*! + \since 4.2 + Returns the indexes in the given \a row for columns where all rows are selected. + + \sa selectedIndexes(), selectedRows() +*/ + +QModelIndexList QItemSelectionModel::selectedColumns(int row) const +{ + QModelIndexList indexes; + //the QSet contains pairs of parent modelIndex + //and column number + QSet< QPair<QModelIndex, int> > columnsSeen; + + const QItemSelection ranges = selection(); + for (int i = 0; i < ranges.count(); ++i) { + const QItemSelectionRange &range = ranges.at(i); + QModelIndex parent = range.parent(); + for (int column = range.left(); column <= range.right(); column++) { + QPair<QModelIndex, int> columnDef = qMakePair(parent, column); + if (!columnsSeen.contains(columnDef)) { + columnsSeen << columnDef; + if (isColumnSelected(column, parent)) { + indexes.append(model()->index(row, column, parent)); + } + } + } + } + + return indexes; +} + +/*! + Returns the selection ranges stored in the selection model. +*/ +const QItemSelection QItemSelectionModel::selection() const +{ + Q_D(const QItemSelectionModel); + QItemSelection selected = d->ranges; + selected.merge(d->currentSelection, d->currentCommand); + int i = 0; + // make sure we have no invalid ranges + // ### should probably be handled more generic somewhere else + while (i<selected.count()) { + if (selected.at(i).isValid()) + ++i; + else + (selected.removeAt(i)); + } + return selected; +} + +/*! + Returns the item model operated on by the selection model. +*/ +const QAbstractItemModel *QItemSelectionModel::model() const +{ + return d_func()->model; +} + +/*! + Compares the two selections \a newSelection and \a oldSelection + and emits selectionChanged() with the deselected and selected items. +*/ +void QItemSelectionModel::emitSelectionChanged(const QItemSelection &newSelection, + const QItemSelection &oldSelection) +{ + // if both selections are empty or equal we return + if ((oldSelection.isEmpty() && newSelection.isEmpty()) || + oldSelection == newSelection) + return; + + // if either selection is empty we do not need to compare + if (oldSelection.isEmpty() || newSelection.isEmpty()) { + emit selectionChanged(newSelection, oldSelection); + return; + } + + QItemSelection deselected = oldSelection; + QItemSelection selected = newSelection; + + // remove equal ranges + bool advance; + for (int o = 0; o < deselected.count(); ++o) { + advance = true; + for (int s = 0; s < selected.count() && o < deselected.count();) { + if (deselected.at(o) == selected.at(s)) { + deselected.removeAt(o); + selected.removeAt(s); + advance = false; + } else { + ++s; + } + } + if (advance) + ++o; + } + + // find intersections + QItemSelection intersections; + for (int o = 0; o < deselected.count(); ++o) { + for (int s = 0; s < selected.count(); ++s) { + if (deselected.at(o).intersects(selected.at(s))) + intersections.append(deselected.at(o).intersected(selected.at(s))); + } + } + + // compare remaining ranges with intersections and split them to find deselected and selected + for (int i = 0; i < intersections.count(); ++i) { + // split deselected + for (int o = 0; o < deselected.count();) { + if (deselected.at(o).intersects(intersections.at(i))) { + QItemSelection::split(deselected.at(o), intersections.at(i), &deselected); + deselected.removeAt(o); + } else { + ++o; + } + } + // split selected + for (int s = 0; s < selected.count();) { + if (selected.at(s).intersects(intersections.at(i))) { + QItemSelection::split(selected.at(s), intersections.at(i), &selected); + selected.removeAt(s); + } else { + ++s; + } + } + } + + emit selectionChanged(selected, deselected); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QItemSelectionRange &range) +{ +#ifndef Q_BROKEN_DEBUG_STREAM + dbg.nospace() << "QItemSelectionRange(" << range.topLeft() + << "," << range.bottomRight() << ")"; + return dbg.space(); +#else + qWarning("This compiler doesn't support streaming QItemSelectionRange to QDebug"); + return dbg; + Q_UNUSED(range); +#endif +} +#endif + +QT_END_NAMESPACE + +#include "moc_qitemselectionmodel.cpp" + +#endif // QT_NO_ITEMVIEWS diff --git a/src/gui/itemviews/qitemselectionmodel.h b/src/gui/itemviews/qitemselectionmodel.h new file mode 100644 index 0000000..24ecf7d --- /dev/null +++ b/src/gui/itemviews/qitemselectionmodel.h @@ -0,0 +1,229 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QITEMSELECTIONMODEL_H +#define QITEMSELECTIONMODEL_H + +#include <QtCore/qset.h> +#include <QtCore/qvector.h> +#include <QtCore/qlist.h> +#include <QtCore/qabstractitemmodel.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_ITEMVIEWS + +class Q_GUI_EXPORT QItemSelectionRange +{ + +public: + inline QItemSelectionRange() {} + inline QItemSelectionRange(const QItemSelectionRange &other) + : tl(other.tl), br(other.br) {} + inline QItemSelectionRange(const QModelIndex &topLeft, const QModelIndex &bottomRight); + explicit inline QItemSelectionRange(const QModelIndex &index) + { tl = index; br = tl; } + + inline int top() const { return tl.row(); } + inline int left() const { return tl.column(); } + inline int bottom() const { return br.row(); } + inline int right() const { return br.column(); } + inline int width() const { return br.column() - tl.column() + 1; } + inline int height() const { return br.row() - tl.row() + 1; } + + inline QModelIndex topLeft() const { return QModelIndex(tl); } + inline QModelIndex bottomRight() const { return QModelIndex(br); } + inline QModelIndex parent() const { return tl.parent(); } + inline const QAbstractItemModel *model() const { return tl.model(); } + + inline bool contains(const QModelIndex &index) const + { + return (parent() == index.parent() + && tl.row() <= index.row() && tl.column() <= index.column() + && br.row() >= index.row() && br.column() >= index.column()); + } + + inline bool contains(int row, int column, const QModelIndex &parentIndex) const + { + return (parent() == parentIndex + && tl.row() <= row && tl.column() <= column + && br.row() >= row && br.column() >= column); + } + + bool intersects(const QItemSelectionRange &other) const; + QItemSelectionRange intersect(const QItemSelectionRange &other) const; // ### Qt 5: make QT4_SUPPORT + inline QItemSelectionRange intersected(const QItemSelectionRange &other) const + { return intersect(other); } + + inline bool operator==(const QItemSelectionRange &other) const + { return (tl == other.tl && br == other.br); } + inline bool operator!=(const QItemSelectionRange &other) const + { return !operator==(other); } + + inline bool isValid() const + { + return (tl.isValid() && br.isValid() && tl.parent() == br.parent() + && top() <= bottom() && left() <= right()); + } + + QModelIndexList indexes() const; + +private: + QPersistentModelIndex tl, br; +}; +Q_DECLARE_TYPEINFO(QItemSelectionRange, Q_MOVABLE_TYPE); + +inline QItemSelectionRange::QItemSelectionRange(const QModelIndex &atopLeft, + const QModelIndex &abottomRight) +{ tl = atopLeft; br = abottomRight; } + +class QItemSelection; +class QItemSelectionModelPrivate; + +class Q_GUI_EXPORT QItemSelectionModel : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QItemSelectionModel) + Q_FLAGS(SelectionFlags) + +public: + + enum SelectionFlag { + NoUpdate = 0x0000, + Clear = 0x0001, + Select = 0x0002, + Deselect = 0x0004, + Toggle = 0x0008, + Current = 0x0010, + Rows = 0x0020, + Columns = 0x0040, + SelectCurrent = Select | Current, + ToggleCurrent = Toggle | Current, + ClearAndSelect = Clear | Select + }; + + Q_DECLARE_FLAGS(SelectionFlags, SelectionFlag) + + explicit QItemSelectionModel(QAbstractItemModel *model); + explicit QItemSelectionModel(QAbstractItemModel *model, QObject *parent); + virtual ~QItemSelectionModel(); + + QModelIndex currentIndex() const; + + bool isSelected(const QModelIndex &index) const; + bool isRowSelected(int row, const QModelIndex &parent) const; + bool isColumnSelected(int column, const QModelIndex &parent) const; + + bool rowIntersectsSelection(int row, const QModelIndex &parent) const; + bool columnIntersectsSelection(int column, const QModelIndex &parent) const; + + bool hasSelection() const; + + QModelIndexList selectedIndexes() const; + QModelIndexList selectedRows(int column = 0) const; + QModelIndexList selectedColumns(int row = 0) const; + const QItemSelection selection() const; + + const QAbstractItemModel *model() const; + +public Q_SLOTS: + void setCurrentIndex(const QModelIndex &index, QItemSelectionModel::SelectionFlags command); + virtual void select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command); + virtual void select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command); + virtual void clear(); + virtual void reset(); + + void clearSelection(); + +Q_SIGNALS: + void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); + void currentRowChanged(const QModelIndex ¤t, const QModelIndex &previous); + void currentColumnChanged(const QModelIndex ¤t, const QModelIndex &previous); + +protected: + QItemSelectionModel(QItemSelectionModelPrivate &dd, QAbstractItemModel *model); + void emitSelectionChanged(const QItemSelection &newSelection, const QItemSelection &oldSelection); + +private: + Q_DISABLE_COPY(QItemSelectionModel) + Q_PRIVATE_SLOT(d_func(), void _q_columnsAboutToBeRemoved(const QModelIndex&, int, int)) + Q_PRIVATE_SLOT(d_func(), void _q_rowsAboutToBeRemoved(const QModelIndex&, int, int)) + Q_PRIVATE_SLOT(d_func(), void _q_columnsAboutToBeInserted(const QModelIndex&, int, int)) + Q_PRIVATE_SLOT(d_func(), void _q_rowsAboutToBeInserted(const QModelIndex&, int, int)) + Q_PRIVATE_SLOT(d_func(), void _q_layoutAboutToBeChanged()) + Q_PRIVATE_SLOT(d_func(), void _q_layoutChanged()) +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QItemSelectionModel::SelectionFlags) + +// dummy implentation of qHash() necessary for instantiating QList<QItemSelectionRange>::toSet() with MSVC +inline uint qHash(const QItemSelectionRange &) { return 0; } + +class Q_GUI_EXPORT QItemSelection : public QList<QItemSelectionRange> +{ +public: + QItemSelection() {} + QItemSelection(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void select(const QModelIndex &topLeft, const QModelIndex &bottomRight); + bool contains(const QModelIndex &index) const; + QModelIndexList indexes() const; + void merge(const QItemSelection &other, QItemSelectionModel::SelectionFlags command); + static void split(const QItemSelectionRange &range, + const QItemSelectionRange &other, + QItemSelection *result); +}; + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QItemSelectionRange &); +#endif + +#endif // QT_NO_ITEMVIEWS + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QITEMSELECTIONMODEL_H diff --git a/src/gui/itemviews/qitemselectionmodel_p.h b/src/gui/itemviews/qitemselectionmodel_p.h new file mode 100644 index 0000000..4c11b9f --- /dev/null +++ b/src/gui/itemviews/qitemselectionmodel_p.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QITEMSELECTIONMODEL_P_H +#define QITEMSELECTIONMODEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qobject_p.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_ITEMVIEWS +class QItemSelectionModelPrivate: public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QItemSelectionModel) +public: + QItemSelectionModelPrivate() + : model(0), + currentCommand(QItemSelectionModel::NoUpdate), + tableSelected(false) {} + + QItemSelection expandSelection(const QItemSelection &selection, + QItemSelectionModel::SelectionFlags command) const; + + void _q_rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + void _q_columnsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + void _q_rowsAboutToBeInserted(const QModelIndex &parent, int start, int end); + void _q_columnsAboutToBeInserted(const QModelIndex &parent, int start, int end); + void _q_layoutAboutToBeChanged(); + void _q_layoutChanged(); + + inline void remove(QList<QItemSelectionRange> &r) + { + QList<QItemSelectionRange>::const_iterator it = r.constBegin(); + for (; it != r.constEnd(); ++it) + ranges.removeAll(*it); + } + + inline void finalize() + { + ranges.merge(currentSelection, currentCommand); + if (!currentSelection.isEmpty()) // ### perhaps this should be in QList + currentSelection.clear(); + } + + QPointer<QAbstractItemModel> model; + QItemSelection ranges; + QItemSelection currentSelection; + QPersistentModelIndex currentIndex; + QItemSelectionModel::SelectionFlags currentCommand; + QList<QPersistentModelIndex> savedPersistentIndexes; + QList<QPersistentModelIndex> savedPersistentCurrentIndexes; + // optimization when all indexes are selected + bool tableSelected; + QPersistentModelIndex tableParent; + int tableColCount, tableRowCount; +}; + +#endif // QT_NO_ITEMVIEWS + +QT_END_NAMESPACE + +#endif // QITEMSELECTIONMODEL_P_H diff --git a/src/gui/itemviews/qlistview.cpp b/src/gui/itemviews/qlistview.cpp new file mode 100644 index 0000000..d2fec21 --- /dev/null +++ b/src/gui/itemviews/qlistview.cpp @@ -0,0 +1,3001 @@ +/*************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlistview.h" + +#ifndef QT_NO_LISTVIEW +#include <qabstractitemdelegate.h> +#include <qapplication.h> +#include <qpainter.h> +#include <qbitmap.h> +#include <qvector.h> +#include <qstyle.h> +#include <qevent.h> +#include <qscrollbar.h> +#include <qrubberband.h> +#include <private/qlistview_p.h> +#include <qdebug.h> +#ifndef QT_NO_ACCESSIBILITY +#include <qaccessible.h> +#endif + +QT_BEGIN_NAMESPACE + +/*! + \class QListView + + \brief The QListView class provides a list or icon view onto a model. + + \ingroup model-view + \ingroup advanced + \mainclass + + A QListView presents items stored in a model, either as a simple + non-hierarchical list, or as a collection of icons. This class is used + to provide lists and icon views that were previously provided by the + \c QListBox and \c QIconView classes, but using the more flexible + approach provided by Qt's model/view architecture. + + The QListView class is one of the \l{Model/View Classes} + and is part of Qt's \l{Model/View Programming}{model/view framework}. + + This view does not display horizontal or vertical headers; to display + a list of items with a horizontal header, use QTreeView instead. + + QListView implements the interfaces defined by the + QAbstractItemView class to allow it to display data provided by + models derived from the QAbstractItemModel class. + + Items in a list view can be displayed using one of two view modes: + In \l ListMode, the items are displayed in the form of a simple list; + in \l IconMode, the list view takes the form of an \e{icon view} in + which the items are displayed with icons like files in a file manager. + By default, the list view is in \l ListMode. To change the view mode, + use the setViewMode() function, and to determine the current view mode, + use viewMode(). + + Items in these views are laid out in the direction specified by the + flow() of the list view. The items may be fixed in place, or allowed + to move, depending on the view's movement() state. + + If the items in the model cannot be completely laid out in the + direction of flow, they can be wrapped at the boundary of the view + widget; this depends on isWrapping(). This property is useful when the + items are being represented by an icon view. + + The resizeMode() and layoutMode() govern how and when the items are + laid out. Items are spaced according to their spacing(), and can exist + within a notional grid of size specified by gridSize(). The items can + be rendered as large or small icons depending on their iconSize(). + + \table 100% + \row \o \inlineimage windowsxp-listview.png Screenshot of a Windows XP style list view + \o \inlineimage macintosh-listview.png Screenshot of a Macintosh style table view + \o \inlineimage plastique-listview.png Screenshot of a Plastique style table view + \row \o A \l{Windows XP Style Widget Gallery}{Windows XP style} list view. + \o A \l{Macintosh Style Widget Gallery}{Macintosh style} list view. + \o A \l{Plastique Style Widget Gallery}{Plastique style} list view. + \endtable + + \section1 Improving Performance + + It is possible to give the view hints about the data it is handling in order + to improve its performance when displaying large numbers of items. One approach + that can be taken for views that are intended to display items with equal sizes + is to set the \l uniformItemSizes property to true. + + \sa {View Classes}, QTreeView, QTableView, QListWidget +*/ + +/*! + \enum QListView::ViewMode + + \value ListMode The items are laid out using TopToBottom flow, with Small size and Static movement + \value IconMode The items are laid out using LeftToRight flow, with Large size and Free movement +*/ + +/*! + \enum QListView::Movement + + \value Static The items cannot be moved by the user. + \value Free The items can be moved freely by the user. + \value Snap The items snap to the specified grid when moved; see + setGridSize(). +*/ + +/*! + \enum QListView::Flow + + \value LeftToRight The items are laid out in the view from the left + to the right. + \value TopToBottom The items are laid out in the view from the top + to the bottom. +*/ + +/*! + \enum QListView::ResizeMode + + \value Fixed The items will only be laid out the first time the view is shown. + \value Adjust The items will be laid out every time the view is resized. +*/ + +/*! + \enum QListView::LayoutMode + + \value SinglePass The items are laid out all at once. + \value Batched The items are laid out in batches of \l batchSize items. + \sa batchSize +*/ + +/*! + \since 4.2 + \fn void QListView::indexesMoved(const QModelIndexList &indexes) + + This signal is emitted when the specified \a indexes are moved in the view. +*/ + +/*! + Creates a new QListView with the given \a parent to view a model. + Use setModel() to set the model. +*/ +QListView::QListView(QWidget *parent) + : QAbstractItemView(*new QListViewPrivate, parent) +{ + setViewMode(ListMode); + setSelectionMode(SingleSelection); + setAttribute(Qt::WA_MacShowFocusRect); + Q_D(QListView); // We rely on a qobject_cast for PM_DefaultFrameWidth to change + d->updateStyledFrameWidths(); // hence we have to force an update now that the object has been constructed +} + +/*! + \internal +*/ +QListView::QListView(QListViewPrivate &dd, QWidget *parent) + : QAbstractItemView(dd, parent) +{ + setViewMode(ListMode); + setSelectionMode(SingleSelection); + setAttribute(Qt::WA_MacShowFocusRect); + Q_D(QListView); // We rely on a qobject_cast for PM_DefaultFrameWidth to change + d->updateStyledFrameWidths(); // hence we have to force an update now that the object has been constructed +} + +/*! + Destroys the view. +*/ +QListView::~QListView() +{ +} + +/*! + \property QListView::movement + \brief whether the items can be moved freely, are snapped to a + grid, or cannot be moved at all. + + This property determines how the user can move the items in the + view. \l Static means that the items can't be moved the user. \l + Free means that the user can drag and drop the items to any + position in the view. \l Snap means that the user can drag and + drop the items, but only to the positions in a notional grid + signified by the gridSize property. + + Setting this property when the view is visible will cause the + items to be laid out again. + + By default, this property is set to \l Static. + + \sa gridSize, resizeMode, viewMode +*/ +void QListView::setMovement(Movement movement) +{ + Q_D(QListView); + d->modeProperties |= uint(QListViewPrivate::Movement); + d->movement = movement; + +#ifndef QT_NO_DRAGANDDROP + bool movable = (movement != Static); + setDragEnabled(movable); + d->viewport->setAcceptDrops(movable); +#endif + d->doDelayedItemsLayout(); +} + +QListView::Movement QListView::movement() const +{ + Q_D(const QListView); + return d->movement; +} + +/*! + \property QListView::flow + \brief which direction the items layout should flow. + + If this property is \l LeftToRight, the items will be laid out left + to right. If the \l isWrapping property is true, the layout will wrap + when it reaches the right side of the visible area. If this + property is \l TopToBottom, the items will be laid out from the top + of the visible area, wrapping when it reaches the bottom. + + Setting this property when the view is visible will cause the + items to be laid out again. + + By default, this property is set to \l TopToBottom. + + \sa viewMode +*/ +void QListView::setFlow(Flow flow) +{ + Q_D(QListView); + d->modeProperties |= uint(QListViewPrivate::Flow); + d->flow = flow; + d->doDelayedItemsLayout(); +} + +QListView::Flow QListView::flow() const +{ + Q_D(const QListView); + return d->flow; +} + +/*! + \property QListView::isWrapping + \brief whether the items layout should wrap. + + This property holds whether the layout should wrap when there is + no more space in the visible area. The point at which the layout wraps + depends on the \l flow property. + + Setting this property when the view is visible will cause the + items to be laid out again. + + By default, this property is false. + + \sa viewMode +*/ +void QListView::setWrapping(bool enable) +{ + Q_D(QListView); + d->modeProperties |= uint(QListViewPrivate::Wrap); + d->setWrapping(enable); + d->doDelayedItemsLayout(); +} + +bool QListView::isWrapping() const +{ + Q_D(const QListView); + return d->isWrapping(); +} + +/*! + \property QListView::resizeMode + \brief whether the items are laid out again when the view is resized. + + If this property is \l Adjust, the items will be laid out again + when the view is resized. If the value is \l Fixed, the items will + not be laid out when the view is resized. + + By default, this property is set to \l Fixed. + + \sa movement, gridSize, viewMode +*/ +void QListView::setResizeMode(ResizeMode mode) +{ + Q_D(QListView); + d->modeProperties |= uint(QListViewPrivate::ResizeMode); + d->resizeMode = mode; +} + +QListView::ResizeMode QListView::resizeMode() const +{ + Q_D(const QListView); + return d->resizeMode; +} + +/*! + \property QListView::layoutMode + \brief determines whether the layout of items should happen immediately or be delayed. + + This property holds the layout mode for the items. When the mode + is \l SinglePass (the default), the items are laid out all in one go. + When the mode is \l Batched, the items are laid out in batches of \l batchSize + items, while processing events. This makes it possible to + instantly view and interact with the visible items while the rest + are being laid out. + + \sa viewMode +*/ +void QListView::setLayoutMode(LayoutMode mode) +{ + Q_D(QListView); + d->layoutMode = mode; +} + +QListView::LayoutMode QListView::layoutMode() const +{ + Q_D(const QListView); + return d->layoutMode; +} + +/*! + \property QListView::spacing + \brief the space between items in the layout + + This property is the size of the empty space that is padded around + an item in the layout. + + Setting this property when the view is visible will cause the + items to be laid out again. + + By default, this property contains a value of 0. + + \sa viewMode +*/ +// ### Qt5: Use same semantic as layouts (spacing is the size of space +// *between* items) +void QListView::setSpacing(int space) +{ + Q_D(QListView); + d->modeProperties |= uint(QListViewPrivate::Spacing); + d->setSpacing(space); + d->doDelayedItemsLayout(); +} + +int QListView::spacing() const +{ + Q_D(const QListView); + return d->spacing(); +} + +/*! + \property QListView::batchSize + \brief the number of items laid out in each batch if \l layoutMode is + set to \l Batched + + The default value is 100. + + \since 4.2 +*/ + +void QListView::setBatchSize(int batchSize) +{ + Q_D(QListView); + if (batchSize <= 0) { + qWarning("Invalid batchSize (%d)", batchSize); + return; + } + d->batchSize = batchSize; +} + +int QListView::batchSize() const +{ + Q_D(const QListView); + return d->batchSize; +} + +/*! + \property QListView::gridSize + \brief the size of the layout grid + + This property is the size of the grid in which the items are laid + out. The default is an empty size which means that there is no + grid and the layout is not done in a grid. Setting this property + to a non-empty size switches on the grid layout. (When a grid + layout is in force the \l spacing property is ignored.) + + Setting this property when the view is visible will cause the + items to be laid out again. + + \sa viewMode +*/ +void QListView::setGridSize(const QSize &size) +{ + Q_D(QListView); + d->modeProperties |= uint(QListViewPrivate::GridSize); + d->setGridSize(size); + d->doDelayedItemsLayout(); +} + +QSize QListView::gridSize() const +{ + Q_D(const QListView); + return d->gridSize(); +} + +/*! + \property QListView::viewMode + \brief the view mode of the QListView. + + This property will change the other unset properties to conform + with the set view mode. QListView-specific properties that have already been set + will not be changed, unless clearPropertyFlags() has been called. + + Setting the view mode will enable or disable drag and drop based on the + selected movement. For ListMode, the default movement is \l Static + (drag and drop disabled); for IconMode, the default movement is + \l Free (drag and drop enabled). + + \sa isWrapping, spacing, gridSize, flow, movement, resizeMode +*/ +void QListView::setViewMode(ViewMode mode) +{ + Q_D(QListView); + d->viewMode = mode; + + if (mode == ListMode) { + delete d->dynamicListView; + d->dynamicListView = 0; + if (!d->staticListView) + d->staticListView = new QStaticListViewBase(this, d); + if (!(d->modeProperties & QListViewPrivate::Wrap)) + d->setWrapping(false); + if (!(d->modeProperties & QListViewPrivate::Spacing)) + d->setSpacing(0); + if (!(d->modeProperties & QListViewPrivate::GridSize)) + d->setGridSize(QSize()); + if (!(d->modeProperties & QListViewPrivate::Flow)) + d->flow = TopToBottom; + if (!(d->modeProperties & QListViewPrivate::Movement)) + d->movement = Static; + if (!(d->modeProperties & QListViewPrivate::ResizeMode)) + d->resizeMode = Fixed; + if (!(d->modeProperties & QListViewPrivate::SelectionRectVisible)) + d->showElasticBand = false; + } else { + delete d->staticListView; + d->staticListView = 0; + if (!d->dynamicListView) + d->dynamicListView = new QDynamicListViewBase(this, d); + if (!(d->modeProperties & QListViewPrivate::Wrap)) + d->setWrapping(true); + if (!(d->modeProperties & QListViewPrivate::Spacing)) + d->setSpacing(0); + if (!(d->modeProperties & QListViewPrivate::GridSize)) + d->setGridSize(QSize()); + if (!(d->modeProperties & QListViewPrivate::Flow)) + d->flow = LeftToRight; + if (!(d->modeProperties & QListViewPrivate::Movement)) + d->movement = Free; + if (!(d->modeProperties & QListViewPrivate::ResizeMode)) + d->resizeMode = Fixed; + if (!(d->modeProperties & QListViewPrivate::SelectionRectVisible)) + d->showElasticBand = true; + } + +#ifndef QT_NO_DRAGANDDROP + bool movable = (d->movement != Static); + setDragEnabled(movable); + setAcceptDrops(movable); +#endif + d->clear(); + d->doDelayedItemsLayout(); +} + +QListView::ViewMode QListView::viewMode() const +{ + Q_D(const QListView); + return d->viewMode; +} + +/*! + Clears the QListView-specific property flags. See \l{viewMode}. + + Properties inherited from QAbstractItemView are not covered by the + property flags. Specifically, \l{QAbstractItemView::dragEnabled} + {dragEnabled} and \l{QAbstractItemView::acceptDrops} + {acceptsDrops} are computed by QListView when calling + setMovement() or setViewMode(). +*/ +void QListView::clearPropertyFlags() +{ + Q_D(QListView); + d->modeProperties = 0; +} + +/*! + Returns true if the \a row is hidden; otherwise returns false. +*/ +bool QListView::isRowHidden(int row) const +{ + Q_D(const QListView); + return d->isHidden(row); +} + +/*! + If \a hide is true, the given \a row will be hidden; otherwise + the \a row will be shown. +*/ +void QListView::setRowHidden(int row, bool hide) +{ + Q_D(QListView); + const bool hidden = d->isHidden(row); + if (d->viewMode == ListMode) { + if (hide && !hidden) + d->hiddenRows.append(d->model->index(row, 0)); + else if (!hide && hidden) + d->hiddenRows.remove(d->hiddenRows.indexOf(d->model->index(row, 0))); + d->doDelayedItemsLayout(); + } else { + if (hide && !hidden) { + d->dynamicListView->removeItem(row); + d->hiddenRows.append(d->model->index(row, 0)); + } else if (!hide && hidden) { + d->hiddenRows.remove(d->hiddenRows.indexOf(d->model->index(row, 0))); + d->dynamicListView->insertItem(row); + } + if (d->resizeMode == Adjust) + d->doDelayedItemsLayout(); + d->viewport->update(); + } +} + +/*! + \reimp +*/ +QRect QListView::visualRect(const QModelIndex &index) const +{ + Q_D(const QListView); + return d->mapToViewport(rectForIndex(index), d->viewMode == QListView::ListMode); +} + +/*! + \reimp +*/ +void QListView::scrollTo(const QModelIndex &index, ScrollHint hint) +{ + Q_D(QListView); + + if (index.parent() != d->root || index.column() != d->column) + return; + + const QRect rect = visualRect(index); + if (hint == EnsureVisible && d->viewport->rect().contains(rect)) { + d->setDirtyRegion(rect); + return; + } + + if (d->flow == QListView::TopToBottom || d->isWrapping()) // vertical + verticalScrollBar()->setValue(d->verticalScrollToValue(index, rect, hint)); + + if (d->flow == QListView::LeftToRight || d->isWrapping()) // horizontal + horizontalScrollBar()->setValue(d->horizontalScrollToValue(index, rect, hint)); +} + +int QListViewPrivate::horizontalScrollToValue(const QModelIndex &index, const QRect &rect, + QListView::ScrollHint hint) const +{ + Q_Q(const QListView); + const QRect area = viewport->rect(); + const bool leftOf = q->isRightToLeft() + ? (rect.left() < area.left()) && (rect.right() < area.right()) + : rect.left() < area.left(); + const bool rightOf = q->isRightToLeft() + ? rect.right() > area.right() + : (rect.right() > area.right()) && (rect.left() > area.left()); + int horizontalValue = q->horizontalScrollBar()->value(); + + // ScrollPerItem + if (q->horizontalScrollMode() == QAbstractItemView::ScrollPerItem && viewMode == QListView::ListMode) { + const QListViewItem item = indexToListViewItem(index); + const QRect rect = q->visualRect(index); + horizontalValue = staticListView->horizontalPerItemValue(itemIndex(item), + horizontalValue, area.width(), + leftOf, rightOf, isWrapping(), hint, rect.width()); + } else { // ScrollPerPixel + if (q->isRightToLeft()) { + if (hint == QListView::PositionAtCenter) { + horizontalValue += ((area.width() - rect.width()) / 2) - rect.left(); + } else { + if (leftOf) + horizontalValue -= rect.left(); + else if (rightOf) + horizontalValue += qMin(rect.left(), area.width() - rect.right()); + } + } else { + if (hint == QListView::PositionAtCenter) { + horizontalValue += rect.left() - ((area.width()- rect.width()) / 2); + } else { + if (leftOf) + horizontalValue += rect.left(); + else if (rightOf) + horizontalValue += qMin(rect.left(), rect.right() - area.width()); + } + } + } + return horizontalValue; +} + +int QListViewPrivate::verticalScrollToValue(const QModelIndex &index, const QRect &rect, + QListView::ScrollHint hint) const +{ + Q_Q(const QListView); + + const QRect area = viewport->rect(); + const bool above = (hint == QListView::EnsureVisible && rect.top() < area.top()); + const bool below = (hint == QListView::EnsureVisible && rect.bottom() > area.bottom()); + + int verticalValue = q->verticalScrollBar()->value(); + + // ScrollPerItem + if (q->verticalScrollMode() == QAbstractItemView::ScrollPerItem && viewMode == QListView::ListMode) { + const QListViewItem item = indexToListViewItem(index); + const QRect rect = q->visualRect(index); + verticalValue = staticListView->verticalPerItemValue(itemIndex(item), + verticalValue, area.height(), + above, below, isWrapping(), hint, rect.height()); + + } else { // ScrollPerPixel + QRect adjusted = rect.adjusted(-spacing(), -spacing(), spacing(), spacing()); + if (hint == QListView::PositionAtTop || above) + verticalValue += adjusted.top(); + else if (hint == QListView::PositionAtBottom || below) + verticalValue += qMin(adjusted.top(), adjusted.bottom() - area.height() + 1); + else if (hint == QListView::PositionAtCenter) + verticalValue += adjusted.top() - ((area.height() - adjusted.height()) / 2); + } + + return verticalValue; +} + +void QListViewPrivate::selectAll(QItemSelectionModel::SelectionFlags command) +{ + if (!selectionModel) + return; + + QItemSelection selection; + QModelIndex topLeft; + int row = 0; + const int colCount = model->columnCount(root); + for(; row < model->rowCount(root); ++row) { + if (isHidden(row)) { + //it might be the end of a selection range + if (topLeft.isValid()) { + QModelIndex bottomRight = model->index(row - 1, colCount - 1, root); + selection.append(QItemSelectionRange(topLeft, bottomRight)); + topLeft = QModelIndex(); + } + continue; + } + + if (!topLeft.isValid()) //start of a new selection range + topLeft = model->index(row, 0, root); + } + + if (topLeft.isValid()) { + //last selected range + QModelIndex bottomRight = model->index(row - 1, colCount - 1, root); + selection.append(QItemSelectionRange(topLeft, bottomRight)); + } + + if (!selection.isEmpty()) + selectionModel->select(selection, command); +} + + +/*! + \internal +*/ +void QListView::reset() +{ + Q_D(QListView); + d->clear(); + d->hiddenRows.clear(); + QAbstractItemView::reset(); +} + +/*! + \internal +*/ +void QListView::setRootIndex(const QModelIndex &index) +{ + Q_D(QListView); + d->column = qBound(0, d->column, d->model->columnCount(index) - 1); + QAbstractItemView::setRootIndex(index); + // sometimes we get an update before reset() is called + d->clear(); + d->hiddenRows.clear(); +} + +/*! + \internal + + Scroll the view contents by \a dx and \a dy. +*/ +void QListView::scrollContentsBy(int dx, int dy) +{ + Q_D(QListView); + + d->delayedAutoScroll.stop(); // auto scroll was canceled by the user scrolling + + if (d->viewMode == ListMode) + d->staticListView->scrollContentsBy(dx, dy); + else if (state() == DragSelectingState) + d->scrollElasticBandBy(isRightToLeft() ? -dx : dx, dy); + + d->scrollContentsBy(isRightToLeft() ? -dx : dx, dy); + + // update the dragged items + if (d->viewMode == IconMode) // ### move to dynamic class + if (!d->dynamicListView->draggedItems.isEmpty()) + d->setDirtyRegion(d->dynamicListView->draggedItemsRect().translated(dx, dy)); +} + +/*! + \internal + + Resize the internal contents to \a width and \a height and set the + scroll bar ranges accordingly. +*/ +void QListView::resizeContents(int width, int height) +{ + Q_D(QListView); + d->setContentsSize(width, height); +} + +/*! + \internal +*/ +QSize QListView::contentsSize() const +{ + Q_D(const QListView); + return d->contentsSize(); +} + +/*! + \reimp +*/ +void QListView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + Q_D(QListView); + if (d->viewMode == IconMode) + d->dynamicListView->dataChanged(topLeft, bottomRight); + QAbstractItemView::dataChanged(topLeft, bottomRight); +} + +/*! + \reimp +*/ +void QListView::rowsInserted(const QModelIndex &parent, int start, int end) +{ + Q_D(QListView); + // ### be smarter about inserted items + d->clear(); + d->doDelayedItemsLayout(); + QAbstractItemView::rowsInserted(parent, start, end); +} + +/*! + \reimp +*/ +void QListView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + Q_D(QListView); + // if the parent is above d->root in the tree, nothing will happen + QAbstractItemView::rowsAboutToBeRemoved(parent, start, end); + if (parent == d->root) { + for (int i = d->hiddenRows.count() - 1; i >= 0; --i) { + int hiddenRow = d->hiddenRows.at(i).row(); + if (hiddenRow >= start && hiddenRow <= end) { + d->hiddenRows.remove(i); + } + } + } + d->clear(); + d->doDelayedItemsLayout(); +} + +/*! + \reimp +*/ +void QListView::mouseMoveEvent(QMouseEvent *e) +{ + Q_D(QListView); + QAbstractItemView::mouseMoveEvent(e); + if (state() == DragSelectingState + && d->showElasticBand + && d->selectionMode != SingleSelection + && d->selectionMode != NoSelection) { + QRect rect(d->pressedPosition, e->pos() + QPoint(horizontalOffset(), verticalOffset())); + rect = rect.normalized(); + d->setDirtyRegion(d->mapToViewport(rect.united(d->elasticBand), d->viewMode == QListView::ListMode)); + d->elasticBand = rect; + } +} + +/*! + \reimp +*/ +void QListView::mouseReleaseEvent(QMouseEvent *e) +{ + Q_D(QListView); + QAbstractItemView::mouseReleaseEvent(e); + // #### move this implementation into a dynamic class + if (d->showElasticBand && d->elasticBand.isValid()) { + d->setDirtyRegion(d->mapToViewport(d->elasticBand, d->viewMode == QListView::ListMode)); + d->elasticBand = QRect(); + } +} + +/*! + \reimp +*/ +void QListView::timerEvent(QTimerEvent *e) +{ + Q_D(QListView); + if (e->timerId() == d->batchLayoutTimer.timerId()) { + if (d->doItemsLayout(d->batchSize)) { // layout is done + d->batchLayoutTimer.stop(); + updateGeometries(); + d->viewport->update(); + } + } + QAbstractItemView::timerEvent(e); +} + +/*! + \reimp +*/ +void QListView::resizeEvent(QResizeEvent *e) +{ + Q_D(QListView); + if (d->delayedPendingLayout) + return; + + QSize delta = e->size() - e->oldSize(); + + if (delta.isNull()) + return; + + bool listWrap = (d->viewMode == ListMode) && d->wrapItemText; + bool flowDimensionChanged = (d->flow == LeftToRight && delta.width() != 0) + || (d->flow == TopToBottom && delta.height() != 0); + + // We post a delayed relayout in the following cases : + // - we're wrapping + // - the state is NoState, we're adjusting and the size has changed in the flowing direction + if (listWrap + || (state() == NoState && d->resizeMode == Adjust && flowDimensionChanged)) { + d->doDelayedItemsLayout(100); // wait 1/10 sec before starting the layout + } else { + QAbstractItemView::resizeEvent(e); + } +} + +#ifndef QT_NO_DRAGANDDROP + +/*! + \reimp +*/ +void QListView::dragMoveEvent(QDragMoveEvent *e) +{ + // ### move implementation to dynamic + Q_D(QListView); + if (e->source() == this && d->viewMode == IconMode) { + // the ignore by default + e->ignore(); + if (d->canDecode(e)) { + // get old dragged items rect + QRect itemsRect = d->dynamicListView->itemsRect(d->dynamicListView->draggedItems); + d->setDirtyRegion(itemsRect.translated(d->dynamicListView->draggedItemsDelta())); + // update position + d->dynamicListView->draggedItemsPos = e->pos(); + // get new items rect + d->setDirtyRegion(itemsRect.translated(d->dynamicListView->draggedItemsDelta())); + // set the item under the cursor to current + QModelIndex index; + if (d->movement == Snap) { + QRect rect(d->dynamicListView->snapToGrid(e->pos() + d->offset()), d->gridSize()); + d->intersectingSet(rect); + index = d->intersectVector.count() > 0 + ? d->intersectVector.last() : QModelIndex(); + } else { + index = indexAt(e->pos()); + } + // check if we allow drops here + if (e->source() == this && d->dynamicListView->draggedItems.contains(index)) + e->accept(); // allow changing item position + else if (d->model->flags(index) & Qt::ItemIsDropEnabled) + e->accept(); // allow dropping on dropenabled items + else if (!index.isValid()) + e->accept(); // allow dropping in empty areas + } + // do autoscrolling + if (d->shouldAutoScroll(e->pos())) + startAutoScroll(); + } else { // not internal + QAbstractItemView::dragMoveEvent(e); + } +} + +/*! + \reimp +*/ +void QListView::dragLeaveEvent(QDragLeaveEvent *e) +{ + // ### move implementation to dynamic + Q_D(QListView); + if (d->viewMode == IconMode) { + d->viewport->update(d->dynamicListView->draggedItemsRect()); // erase the area + d->dynamicListView->draggedItemsPos = QPoint(-1, -1); // don't draw the dragged items + } + QAbstractItemView::dragLeaveEvent(e); +} + +/*! + \reimp +*/ +void QListView::dropEvent(QDropEvent *event) +{ + Q_D(QListView); + if (event->source() == this && d->viewMode == IconMode) + internalDrop(event); // ### move to dynamic + else + QAbstractItemView::dropEvent(event); +} + +/*! + \reimp +*/ +void QListView::startDrag(Qt::DropActions supportedActions) +{ + Q_D(QListView); + if (d->viewMode == IconMode) // ### move to dynamic + internalDrag(supportedActions); + else + QAbstractItemView::startDrag(supportedActions); +} + +/*! + \internal + + Called whenever items from the view is dropped on the viewport. + The \a event provides additional information. +*/ +void QListView::internalDrop(QDropEvent *event) +{ + Q_D(QListView); + if (d->viewMode == QListView::ListMode) + return; + + // ### move to dynamic class + QPoint offset(horizontalOffset(), verticalOffset()); + QPoint end = event->pos() + offset; + QPoint start = d->pressedPosition; + QPoint delta = (d->movement == Snap ? + d->dynamicListView->snapToGrid(end) + - d->dynamicListView->snapToGrid(start) : end - start); + QSize contents = d->contentsSize(); + QList<QModelIndex> indexes = d->selectionModel->selectedIndexes(); + for (int i = 0; i < indexes.count(); ++i) { + QModelIndex index = indexes.at(i); + QRect rect = rectForIndex(index); + d->setDirtyRegion(d->mapToViewport(rect, d->viewMode == QListView::ListMode)); + QPoint dest = rect.topLeft() + delta; + if (isRightToLeft()) + dest.setX(d->flipX(dest.x()) - rect.width()); + d->dynamicListView->moveItem(index.row(), dest); + d->setDirtyRegion(visualRect(index)); + } + stopAutoScroll(); + d->dynamicListView->draggedItems.clear(); + emit indexesMoved(indexes); + event->accept(); // we have handled the event + // if the size has not grown, we need to check if it has shrinked + if (d->dynamicListView + && (d->contentsSize().width() <= contents.width() + || d->contentsSize().height() <= contents.height())) { + d->dynamicListView->updateContentsSize(); + } + if (d->contentsSize() != contents) + updateGeometries(); +} + +/*! + \internal + + Called whenever the user starts dragging items and the items are movable, + enabling internal dragging and dropping of items. +*/ +void QListView::internalDrag(Qt::DropActions supportedActions) +{ + Q_D(QListView); + if (d->viewMode == QListView::ListMode) + return; + + // #### move to dynamic class + + // This function does the same thing as in QAbstractItemView::startDrag(), + // plus adding viewitems to the draggedItems list. + // We need these items to draw the drag items + QModelIndexList indexes = d->selectionModel->selectedIndexes(); + if (indexes.count() > 0 ) { + if (d->viewport->acceptDrops()) { + QModelIndexList::ConstIterator it = indexes.constBegin(); + for (; it != indexes.constEnd(); ++it) + if (d->model->flags(*it) & Qt::ItemIsDragEnabled + && (*it).column() == d->column) + d->dynamicListView->draggedItems.push_back(*it); + } + QDrag *drag = new QDrag(this); + drag->setMimeData(d->model->mimeData(indexes)); + Qt::DropAction action = drag->exec(supportedActions, Qt::CopyAction); + d->dynamicListView->draggedItems.clear(); + if (action == Qt::MoveAction) + d->clearOrRemove(); + } +} + +#endif // QT_NO_DRAGANDDROP + +/*! + \reimp +*/ +QStyleOptionViewItem QListView::viewOptions() const +{ + Q_D(const QListView); + QStyleOptionViewItem option = QAbstractItemView::viewOptions(); + if (!d->iconSize.isValid()) { // otherwise it was already set in abstractitemview + int pm = (d->viewMode == ListMode + ? style()->pixelMetric(QStyle::PM_ListViewIconSize, 0, this) + : style()->pixelMetric(QStyle::PM_IconViewIconSize, 0, this)); + option.decorationSize = QSize(pm, pm); + } + if (d->viewMode == IconMode) { + option.showDecorationSelected = false; + option.decorationPosition = QStyleOptionViewItem::Top; + option.displayAlignment = Qt::AlignCenter; + } else { + option.decorationPosition = QStyleOptionViewItem::Left; + } + return option; +} + +/*! + \reimp +*/ +void QListView::paintEvent(QPaintEvent *e) +{ + Q_D(QListView); + if (!d->itemDelegate) + return; + QStyleOptionViewItemV4 option = d->viewOptionsV4(); + QPainter painter(d->viewport); + QRect area = e->rect(); + + QVector<QModelIndex> toBeRendered; +// QVector<QRect> rects = e->region().rects(); +// for (int i = 0; i < rects.size(); ++i) { +// d->intersectingSet(rects.at(i).translated(horizontalOffset(), verticalOffset())); +// toBeRendered += d->intersectVector; +// } + d->intersectingSet(e->rect().translated(horizontalOffset(), verticalOffset()), false); + toBeRendered = d->intersectVector; + + const QModelIndex current = currentIndex(); + const QModelIndex hover = d->hover; + const QAbstractItemModel *itemModel = d->model; + const QItemSelectionModel *selections = d->selectionModel; + const bool focus = (hasFocus() || d->viewport->hasFocus()) && current.isValid(); + const bool alternate = d->alternatingColors; + const QStyle::State state = option.state; + const QAbstractItemView::State viewState = this->state(); + const bool enabled = (state & QStyle::State_Enabled) != 0; + + bool alternateBase = false; + int previousRow = -2; // trigger the alternateBase adjustment on first pass + + QVector<QModelIndex>::const_iterator end = toBeRendered.constEnd(); + for (QVector<QModelIndex>::const_iterator it = toBeRendered.constBegin(); it != end; ++it) { + Q_ASSERT((*it).isValid()); + option.rect = visualRect(*it); + option.state = state; + if (selections && selections->isSelected(*it)) + option.state |= QStyle::State_Selected; + if (enabled) { + QPalette::ColorGroup cg; + if ((itemModel->flags(*it) & Qt::ItemIsEnabled) == 0) { + option.state &= ~QStyle::State_Enabled; + cg = QPalette::Disabled; + } else { + cg = QPalette::Normal; + } + option.palette.setCurrentColorGroup(cg); + } + if (focus && current == *it) { + option.state |= QStyle::State_HasFocus; + if (viewState == EditingState) + option.state |= QStyle::State_Editing; + } + if (*it == hover) + option.state |= QStyle::State_MouseOver; + else + option.state &= ~QStyle::State_MouseOver; + + if (alternate) { + int row = (*it).row(); + if (row != previousRow + 1) { + // adjust alternateBase according to rows in the "gap" + if (!d->hiddenRows.isEmpty()) { + for (int r = qMax(previousRow + 1, 0); r < row; ++r) { + if (!d->isHidden(r)) + alternateBase = !alternateBase; + } + } else { + alternateBase = (row & 1) != 0; + } + } + if (alternateBase) { + option.features |= QStyleOptionViewItemV2::Alternate; + } else { + option.features &= ~QStyleOptionViewItemV2::Alternate; + } + + // draw background of the item (only alternate row). rest of the background + // is provided by the delegate + QStyle::State oldState = option.state; + option.state &= ~QStyle::State_Selected; + style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &option, &painter, this); + option.state = oldState; + + alternateBase = !alternateBase; + previousRow = row; + } + + if (const QWidget *widget = d->editorForIndex(*it).editor) { + QRegion itemGeometry(option.rect); + QRegion widgetGeometry(widget->geometry()); + painter.save(); + painter.setClipRegion(itemGeometry.subtracted(widgetGeometry)); + d->delegateForIndex(*it)->paint(&painter, option, *it); + painter.restore(); + } else { + d->delegateForIndex(*it)->paint(&painter, option, *it); + } + } + +#ifndef QT_NO_DRAGANDDROP + // #### move this implementation into a dynamic class + if (d->viewMode == IconMode) + if (!d->dynamicListView->draggedItems.isEmpty() + && d->viewport->rect().contains(d->dynamicListView->draggedItemsPos)) { + QPoint delta = d->dynamicListView->draggedItemsDelta(); + painter.translate(delta.x(), delta.y()); + d->dynamicListView->drawItems(&painter, d->dynamicListView->draggedItems); + } + // FIXME: Until the we can provide a proper drop indicator + // in IconMode, it makes no sense to show it + if (d->viewMode == ListMode) + d->paintDropIndicator(&painter); +#endif + +#ifndef QT_NO_RUBBERBAND + // #### move this implementation into a dynamic class + if (d->showElasticBand && d->elasticBand.isValid()) { + QStyleOptionRubberBand opt; + opt.initFrom(this); + opt.shape = QRubberBand::Rectangle; + opt.opaque = false; + opt.rect = d->mapToViewport(d->elasticBand, false).intersected( + d->viewport->rect().adjusted(-16, -16, 16, 16)); + painter.save(); + style()->drawControl(QStyle::CE_RubberBand, &opt, &painter); + painter.restore(); + } +#endif +} + +/*! + \reimp +*/ +QModelIndex QListView::indexAt(const QPoint &p) const +{ + Q_D(const QListView); + QRect rect(p.x() + horizontalOffset(), p.y() + verticalOffset(), 1, 1); + d->intersectingSet(rect); + QModelIndex index = d->intersectVector.count() > 0 + ? d->intersectVector.last() : QModelIndex(); + if (index.isValid() && visualRect(index).contains(p)) + return index; + return QModelIndex(); +} + +/*! + \reimp +*/ +int QListView::horizontalOffset() const +{ + Q_D(const QListView); + // ### split into static and dynamic + if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem && d->viewMode == ListMode) { + if (d->isWrapping()) { + if (d->flow == TopToBottom && !d->staticListView->segmentPositions.isEmpty()) { + const int max = d->staticListView->segmentPositions.count() - 1; + int currentValue = qBound(0, horizontalScrollBar()->value(), max); + int position = d->staticListView->segmentPositions.at(currentValue); + int maximumValue = qBound(0, horizontalScrollBar()->maximum(), max); + int maximum = d->staticListView->segmentPositions.at(maximumValue); + return (isRightToLeft() ? maximum - position : position); + } + //return 0; + } else { + if (d->flow == LeftToRight && !d->staticListView->flowPositions.isEmpty()) { + int position = d->staticListView->flowPositions.at(horizontalScrollBar()->value()); + int maximum = d->staticListView->flowPositions.at(horizontalScrollBar()->maximum()); + return (isRightToLeft() ? maximum - position : position); + } + //return 0; + } + } + return (isRightToLeft() + ? horizontalScrollBar()->maximum() - horizontalScrollBar()->value() + : horizontalScrollBar()->value()); +} + +/*! + \reimp +*/ +int QListView::verticalOffset() const +{ + // ## split into static and dynamic + Q_D(const QListView); + if (verticalScrollMode() == QAbstractItemView::ScrollPerItem && d->viewMode == ListMode) { + if (d->isWrapping()) { + if (d->flow == LeftToRight && !d->staticListView->segmentPositions.isEmpty()) { + int value = verticalScrollBar()->value(); + if (value >= d->staticListView->segmentPositions.count()) { + //qWarning("QListView: Vertical scroll bar is out of bounds"); + return 0; + } + return d->staticListView->segmentPositions.at(value); + } + } else { + if (d->flow == TopToBottom && !d->staticListView->flowPositions.isEmpty()) { + int value = verticalScrollBar()->value(); + if (value > d->staticListView->flowPositions.count()) { + //qWarning("QListView: Vertical scroll bar is out of bounds"); + return 0; + } + return d->staticListView->flowPositions.at(value) - d->spacing(); + } + } + } + return verticalScrollBar()->value(); +} + +/*! + \reimp +*/ +QModelIndex QListView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) +{ + Q_D(QListView); + Q_UNUSED(modifiers); + + QModelIndex current = currentIndex(); + if (!current.isValid()) { + int rowCount = d->model->rowCount(d->root); + if (!rowCount) + return QModelIndex(); + int row = 0; + while (row < rowCount && d->isHiddenOrDisabled(row)) + ++row; + if (row >= rowCount) + return QModelIndex(); + return d->model->index(row, 0, d->root); + } + + const QRect initialRect = rectForIndex(current); + QRect rect = initialRect; + if (rect.isEmpty()) { + return d->model->index(0, 0, d->root); + } + if (d->gridSize().isValid()) rect.setSize(d->gridSize()); + + QSize contents = d->contentsSize(); + d->intersectVector.clear(); + + switch (cursorAction) { + case MoveLeft: + while (d->intersectVector.isEmpty()) { + rect.translate(-rect.width(), 0); + if (rect.right() <= 0) + return current; + if (rect.left() < 0) + rect.setLeft(0); + d->intersectingSet(rect); + d->removeCurrentAndDisabled(&d->intersectVector, current); + } + return d->closestIndex(initialRect, d->intersectVector); + case MoveRight: + while (d->intersectVector.isEmpty()) { + rect.translate(rect.width(), 0); + if (rect.left() >= contents.width()) + return current; + if (rect.right() > contents.width()) + rect.setRight(contents.width()); + d->intersectingSet(rect); + d->removeCurrentAndDisabled(&d->intersectVector, current); + } + return d->closestIndex(initialRect, d->intersectVector); + case MovePageUp: + rect.moveTop(rect.top() - d->viewport->height()); + if (rect.top() < rect.height()) + rect.moveTop(rect.height()); + case MovePrevious: + case MoveUp: + while (d->intersectVector.isEmpty()) { + rect.translate(0, -rect.height()); + if (rect.bottom() <= 0) { +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled()) { + int row = d->batchStartRow() - 1; + while (row >= 0 && d->isHiddenOrDisabled(row)) + --row; + if (row >= 0) + return d->model->index(row, d->column, d->root); + } +#endif + return current; + } + if (rect.top() < 0) + rect.setTop(0); + d->intersectingSet(rect); + d->removeCurrentAndDisabled(&d->intersectVector, current); + } + return d->closestIndex(initialRect, d->intersectVector); + case MovePageDown: + rect.moveTop(rect.top() + d->viewport->height()); + if (rect.bottom() > contents.height() - rect.height()) + rect.moveBottom(contents.height() - rect.height()); + case MoveNext: + case MoveDown: + while (d->intersectVector.isEmpty()) { + rect.translate(0, rect.height()); + if (rect.top() >= contents.height()) { +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled()) { + int rowCount = d->model->rowCount(d->root); + int row = 0; + while (row < rowCount && d->isHiddenOrDisabled(row)) + ++row; + if (row < rowCount) + return d->model->index(row, d->column, d->root); + } +#endif + return current; + } + if (rect.bottom() > contents.height()) + rect.setBottom(contents.height()); + d->intersectingSet(rect); + d->removeCurrentAndDisabled(&d->intersectVector, current); + } + return d->closestIndex(initialRect, d->intersectVector); + case MoveHome: + return d->model->index(0, d->column, d->root); + case MoveEnd: + return d->model->index(d->batchStartRow() - 1, d->column, d->root);} + + return current; +} + +/*! + Returns the rectangle of the item at position \a index in the + model. The rectangle is in contents coordinates. + + \sa visualRect() +*/ +QRect QListView::rectForIndex(const QModelIndex &index) const +{ + Q_D(const QListView); + if (!d->isIndexValid(index) + || index.parent() != d->root + || index.column() != d->column + || isIndexHidden(index)) + return QRect(); + d->executePostedLayout(); + QListViewItem item = d->indexToListViewItem(index); + return d->viewItemRect(item); +} + +/*! + \since 4.1 + + Sets the contents position of the item at \a index in the model to the given + \a position. + If the list view's movement mode is Static, this function will have no + effect. +*/ +void QListView::setPositionForIndex(const QPoint &position, const QModelIndex &index) +{ + Q_D(QListView); + if (d->movement == Static + || !d->isIndexValid(index) + || index.parent() != d->root + || index.column() != d->column) + return; + + d->executePostedLayout(); + if (index.row() >= d->dynamicListView->items.count()) + return; + const QSize oldContents = d->contentsSize(); + d->setDirtyRegion(visualRect(index)); // update old position + d->dynamicListView->moveItem(index.row(), position); + d->setDirtyRegion(visualRect(index)); // update new position + + if (d->contentsSize() != oldContents) + updateGeometries(); // update the scroll bars +} + +/*! + \reimp +*/ +void QListView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command) +{ + Q_D(QListView); + if (!d->selectionModel) + return; + + // if we are wrapping, we can only selecte inside the contents rectangle + int w = qMax(d->contentsSize().width(), d->viewport->width()); + int h = qMax(d->contentsSize().height(), d->viewport->height()); + if (d->wrap && !QRect(0, 0, w, h).intersects(rect)) + return; + + QItemSelection selection; + + if (rect.width() == 1 && rect.height() == 1) { + d->intersectingSet(rect.translated(horizontalOffset(), verticalOffset())); + QModelIndex tl; + if (!d->intersectVector.isEmpty()) + tl = d->intersectVector.last(); // special case for mouse press; only select the top item + if (tl.isValid() && d->isIndexEnabled(tl)) + selection.select(tl, tl); + } else { + if (state() == DragSelectingState) { // visual selection mode (rubberband selection) + selection = d->selection(rect.translated(horizontalOffset(), verticalOffset())); + } else { // logical selection mode (key and mouse click selection) + QModelIndex tl, br; + // get the first item + const QRect topLeft(rect.left() + horizontalOffset(), rect.top() + verticalOffset(), 1, 1); + d->intersectingSet(topLeft); + if (!d->intersectVector.isEmpty()) + tl = d->intersectVector.last(); + // get the last item + const QRect bottomRight(rect.right() + horizontalOffset(), rect.bottom() + verticalOffset(), 1, 1); + d->intersectingSet(bottomRight); + if (!d->intersectVector.isEmpty()) + br = d->intersectVector.last(); + + // get the ranges + if (tl.isValid() && br.isValid() + && d->isIndexEnabled(tl) + && d->isIndexEnabled(br)) { + QRect first = rectForIndex(tl); + QRect last = rectForIndex(br); + QRect middle; + if (d->flow == LeftToRight) { + QRect &top = first; + QRect &bottom = last; + // if bottom is above top, swap them + if (top.center().y() > bottom.center().y()) { + QRect tmp = top; + top = bottom; + bottom = tmp; + } + // if the rect are on differnet lines, expand + if (top.top() != bottom.top()) { + // top rectangle + if (isRightToLeft()) + top.setLeft(0); + else + top.setRight(contentsSize().width()); + // bottom rectangle + if (isRightToLeft()) + bottom.setRight(contentsSize().width()); + else + bottom.setLeft(0); + } else if (top.left() > bottom.right()) { + if (isRightToLeft()) + bottom.setLeft(top.right()); + else + bottom.setRight(top.left()); + } else { + if (isRightToLeft()) + top.setLeft(bottom.right()); + else + top.setRight(bottom.left()); + } + // middle rectangle + if (top.bottom() < bottom.top()) { + middle.setTop(top.bottom() + 1); + middle.setLeft(qMin(top.left(), bottom.left())); + middle.setBottom(bottom.top() - 1); + middle.setRight(qMax(top.right(), bottom.right())); + } + } else { // TopToBottom + QRect &left = first; + QRect &right = last; + if (left.center().x() > right.center().x()) + qSwap(left, right); + + int ch = contentsSize().height(); + if (left.left() != right.left()) { + // left rectangle + if (isRightToLeft()) + left.setTop(0); + else + left.setBottom(ch); + + // top rectangle + if (isRightToLeft()) + right.setBottom(ch); + else + right.setTop(0); + // only set middle if the + middle.setTop(0); + middle.setBottom(ch); + middle.setLeft(left.right() + 1); + middle.setRight(right.left() - 1); + } else if (left.bottom() < right.top()) { + left.setBottom(right.top() - 1); + } else { + right.setBottom(left.top() - 1); + } + } + + // do the selections + QItemSelection topSelection = d->selection(first); + QItemSelection middleSelection = d->selection(middle); + QItemSelection bottomSelection = d->selection(last); + // merge + selection.merge(topSelection, QItemSelectionModel::Select); + selection.merge(middleSelection, QItemSelectionModel::Select); + selection.merge(bottomSelection, QItemSelectionModel::Select); + } + } + } + + d->selectionModel->select(selection, command); +} + +/*! + \reimp +*/ +QRegion QListView::visualRegionForSelection(const QItemSelection &selection) const +{ + Q_D(const QListView); + // ### NOTE: this is a potential bottleneck in non-static mode + int c = d->column; + QRegion selectionRegion; + for (int i = 0; i < selection.count(); ++i) { + if (!selection.at(i).isValid()) + continue; + QModelIndex parent = selection.at(i).topLeft().parent(); + int t = selection.at(i).topLeft().row(); + int b = selection.at(i).bottomRight().row(); + if (d->viewMode == IconMode || d->isWrapping()) { // in non-static mode, we have to go through all selected items + for (int r = t; r <= b; ++r) + selectionRegion += QRegion(visualRect(d->model->index(r, c, parent))); + } else { // in static mode, we can optimize a bit + while (t <= b && d->isHidden(t)) ++t; + while (b >= t && d->isHidden(b)) --b; + const QModelIndex top = d->model->index(t, c, d->root); + const QModelIndex bottom = d->model->index(b, c, d->root); + QRect rect(visualRect(top).topLeft(), + visualRect(bottom).bottomRight()); + selectionRegion += QRegion(rect); + } + } + + return selectionRegion; +} + +/*! + \reimp +*/ +QModelIndexList QListView::selectedIndexes() const +{ + Q_D(const QListView); + QModelIndexList viewSelected; + QModelIndexList modelSelected; + if (d->selectionModel) + modelSelected = d->selectionModel->selectedIndexes(); + for (int i = 0; i < modelSelected.count(); ++i) { + QModelIndex index = modelSelected.at(i); + if (!isIndexHidden(index) && index.parent() == d->root && index.column() == d->column) + viewSelected.append(index); + } + return viewSelected; +} + +/*! + \internal + + Layout the items according to the flow and wrapping properties. +*/ +void QListView::doItemsLayout() +{ + Q_D(QListView); + // showing the scroll bars will trigger a resize event, + // so we set the state to expanding to avoid + // triggering another layout + QAbstractItemView::State oldState = state(); + setState(ExpandingState); + if (d->model->columnCount(d->root) > 0) { // no columns means no contents + d->resetBatchStartRow(); + if (layoutMode() == SinglePass) + d->doItemsLayout(d->model->rowCount(d->root)); // layout everything + else if (!d->batchLayoutTimer.isActive()) { + if (!d->doItemsLayout(d->batchSize)) // layout is done + d->batchLayoutTimer.start(0, this); // do a new batch as fast as possible + } + } + QAbstractItemView::doItemsLayout(); + setState(oldState); // restoring the oldState +} + +/*! + \reimp +*/ +void QListView::updateGeometries() +{ + Q_D(QListView); + if (d->model->rowCount(d->root) <= 0 || d->model->columnCount(d->root) <= 0) { + horizontalScrollBar()->setRange(0, 0); + verticalScrollBar()->setRange(0, 0); + } else { + QModelIndex index = d->model->index(0, d->column, d->root); + QStyleOptionViewItemV4 option = d->viewOptionsV4(); + QSize step = d->itemSize(option, index); + + QSize csize = d->contentsSize(); + QSize vsize = d->viewport->size(); + QSize max = maximumViewportSize(); + if (max.width() >= d->contentsSize().width() && max.height() >= d->contentsSize().height()) + vsize = max; + + // ### reorder the logic + + // ### split into static and dynamic + + const bool vertical = verticalScrollMode() == QAbstractItemView::ScrollPerItem; + const bool horizontal = horizontalScrollMode() == QAbstractItemView::ScrollPerItem; + + if (d->flow == TopToBottom) { + if (horizontal && d->isWrapping() && d->viewMode == ListMode) { + const QVector<int> segmentPositions = d->staticListView->segmentPositions; + const int steps = segmentPositions.count() - 1; + if (steps > 0) { + int pageSteps = d->staticListView->perItemScrollingPageSteps(vsize.width(), + csize.width(), + isWrapping()); + horizontalScrollBar()->setSingleStep(1); + horizontalScrollBar()->setPageStep(pageSteps); + horizontalScrollBar()->setRange(0, steps - pageSteps); + } else { + horizontalScrollBar()->setRange(0, 0); + } + } else { + horizontalScrollBar()->setSingleStep(step.width() + d->spacing()); + horizontalScrollBar()->setPageStep(vsize.width()); + horizontalScrollBar()->setRange(0, d->contentsSize().width() - vsize.width()); + } + if (vertical && !d->isWrapping() && d->viewMode == ListMode) { + const QVector<int> flowPositions = d->staticListView->flowPositions; + const int steps = flowPositions.count() - 1; + if (steps > 0) { + int pageSteps = d->staticListView->perItemScrollingPageSteps(vsize.height(), + csize.height(), + isWrapping()); + verticalScrollBar()->setSingleStep(1); + verticalScrollBar()->setPageStep(pageSteps); + verticalScrollBar()->setRange(0, steps - pageSteps); + } else { + verticalScrollBar()->setRange(0, 0); + } + // } else if (vertical && d->isWrapping() && d->movement == Static) { + // ### wrapped scrolling in flow direction + } else { + verticalScrollBar()->setSingleStep(step.height() + d->spacing()); + verticalScrollBar()->setPageStep(vsize.height()); + verticalScrollBar()->setRange(0, d->contentsSize().height() - vsize.height()); + } + } else { // LeftToRight + if (horizontal && !d->isWrapping() && d->viewMode == ListMode) { + const QVector<int> flowPositions = d->staticListView->flowPositions; + int steps = flowPositions.count() - 1; + if (steps > 0) { + int pageSteps = d->staticListView->perItemScrollingPageSteps(vsize.width(), + csize.width(), + isWrapping()); + horizontalScrollBar()->setSingleStep(1); + horizontalScrollBar()->setPageStep(pageSteps); + horizontalScrollBar()->setRange(0, steps - pageSteps); + } else { + horizontalScrollBar()->setRange(0, 0); + } + // } else if (horizontal && d->isWrapping() && d->movement == Static) { + // ### wrapped scrolling in flow direction + } else { + horizontalScrollBar()->setSingleStep(step.width() + d->spacing()); + horizontalScrollBar()->setPageStep(vsize.width()); + horizontalScrollBar()->setRange(0, d->contentsSize().width() - vsize.width()); + } + if (vertical && d->isWrapping() && d->viewMode == ListMode) { + const QVector<int> segmentPositions = d->staticListView->segmentPositions; + int steps = segmentPositions.count() - 1; + if (steps > 0) { + int pageSteps = d->staticListView->perItemScrollingPageSteps(vsize.height(), + csize.height(), + isWrapping()); + verticalScrollBar()->setSingleStep(1); + verticalScrollBar()->setPageStep(pageSteps); + verticalScrollBar()->setRange(0, steps - pageSteps); + } else { + verticalScrollBar()->setRange(0, 0); + } + } else { + verticalScrollBar()->setSingleStep(step.height() + d->spacing()); + verticalScrollBar()->setPageStep(vsize.height()); + verticalScrollBar()->setRange(0, d->contentsSize().height() - vsize.height()); + } + } + } + + QAbstractItemView::updateGeometries(); + + // if the scroll bars are turned off, we resize the contents to the viewport + if (d->movement == Static && !d->isWrapping()) { + d->layoutChildren(); // we need the viewport size to be updated + if (d->flow == TopToBottom) { + if (horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) { + d->setContentsSize(viewport()->width(), contentsSize().height()); + horizontalScrollBar()->setRange(0, 0); // we see all the contents anyway + } + } else { // LeftToRight + if (verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) { + d->setContentsSize(contentsSize().width(), viewport()->height()); + verticalScrollBar()->setRange(0, 0); // we see all the contents anyway + } + } + } +} + +/*! + \reimp +*/ +bool QListView::isIndexHidden(const QModelIndex &index) const +{ + Q_D(const QListView); + return (d->isHidden(index.row()) + && (index.parent() == d->root) + && index.column() == d->column); +} + +/*! + \property QListView::modelColumn + \brief the column in the model that is visible + + By default, this property contains 0, indicating that the first + column in the model will be shown. +*/ +void QListView::setModelColumn(int column) +{ + Q_D(QListView); + if (column < 0 || column >= d->model->columnCount(d->root)) + return; + d->column = column; + d->doDelayedItemsLayout(); +} + +int QListView::modelColumn() const +{ + Q_D(const QListView); + return d->column; +} + +/*! + \property QListView::uniformItemSizes + \brief whether all items in the listview have the same size + \since 4.1 + + This property should only be set to true if it is guaranteed that all items + in the view have the same size. This enables the view to do some + optimizations for performance purposes. + + By default, this property is false. +*/ +void QListView::setUniformItemSizes(bool enable) +{ + Q_D(QListView); + d->uniformItemSizes = enable; +} + +bool QListView::uniformItemSizes() const +{ + Q_D(const QListView); + return d->uniformItemSizes; +} + +/*! + \property QListView::wordWrap + \brief the item text word-wrapping policy + \since 4.2 + + If this property is true then the item text is wrapped where + necessary at word-breaks; otherwise it is not wrapped at all. + This property is false by default. + + Please note that even if wrapping is enabled, the cell will not be + expanded to make room for the text. It will print ellipsis for + text that cannot be shown, according to the view's + \l{QAbstractItemView::}{textElideMode}. +*/ +void QListView::setWordWrap(bool on) +{ + Q_D(QListView); + if (d->wrapItemText == on) + return; + d->wrapItemText = on; + d->doDelayedItemsLayout(); +} + +bool QListView::wordWrap() const +{ + Q_D(const QListView); + return d->wrapItemText; +} + +/*! + \property QListView::selectionRectVisible + \brief if the selection rectangle should be visible + \since 4.3 + + If this property is true then the selection rectangle is visible; + otherwise it will be hidden. + + \note The selection rectangle will only be visible if the selection mode + is in a mode where more than one item can be selected; i.e., it will not + draw a selection rectangle if the selection mode is + QAbstractItemView::SingleSelection. + + By default, this property is false. +*/ +void QListView::setSelectionRectVisible(bool show) +{ + Q_D(QListView); + d->modeProperties |= uint(QListViewPrivate::SelectionRectVisible); + d->setSelectionRectVisible(show); +} + +bool QListView::isSelectionRectVisible() const +{ + Q_D(const QListView); + return d->isSelectionRectVisible(); +} + +/*! + \reimp +*/ +bool QListView::event(QEvent *e) +{ + return QAbstractItemView::event(e); +} + +/* + * private object implementation + */ + +QListViewPrivate::QListViewPrivate() + : QAbstractItemViewPrivate(), + dynamicListView(0), + staticListView(0), + wrap(false), + space(0), + flow(QListView::TopToBottom), + movement(QListView::Static), + resizeMode(QListView::Fixed), + layoutMode(QListView::SinglePass), + viewMode(QListView::ListMode), + modeProperties(0), + column(0), + uniformItemSizes(false), + batchSize(100) +{ +} + +QListViewPrivate::~QListViewPrivate() +{ + delete staticListView; + delete dynamicListView; +} + +void QListViewPrivate::clear() +{ + // ### split into dynamic and static + // initialization of data structs + cachedItemSize = QSize(); + if (viewMode == QListView::ListMode) + staticListView->clear(); + else + dynamicListView->clear(); +} + +void QListViewPrivate::prepareItemsLayout() +{ + Q_Q(QListView); + clear(); + + //take the size as if there were scrollbar in order to prevent scrollbar to blink + layoutBounds = QRect(QPoint(0,0), q->maximumViewportSize()); + + int frameAroundContents = 0; + if (q->style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) + frameAroundContents = q->style()->pixelMetric(QStyle::PM_DefaultFrameWidth) * 2; + int verticalMargin = vbarpolicy==Qt::ScrollBarAlwaysOff ? 0 : + q->style()->pixelMetric(QStyle::PM_ScrollBarExtent, 0, q->verticalScrollBar()) + frameAroundContents; + int horizontalMargin = hbarpolicy==Qt::ScrollBarAlwaysOff ? 0 : + q->style()->pixelMetric(QStyle::PM_ScrollBarExtent, 0, q->horizontalScrollBar()) + frameAroundContents; + + layoutBounds.adjust(0, 0, -verticalMargin, -horizontalMargin); + + int rowCount = model->rowCount(root); + int colCount = model->columnCount(root); + if (colCount <= 0) + rowCount = 0; // no contents + if (viewMode == QListView::ListMode) { + staticListView->flowPositions.resize(rowCount); + } else { + dynamicListView->tree.create(qMax(rowCount - hiddenRows.count(), 0)); + } +} + +/*! + \internal +*/ +bool QListViewPrivate::doItemsLayout(int delta) +{ + // ### split into static and dynamic + int max = model->rowCount(root) - 1; + int first = batchStartRow(); + int last = qMin(first + delta - 1, max); + + if (max < 0 || last < first) + return true; // nothing to do + + if (first == 0) { + layoutChildren(); // make sure the viewport has the right size + prepareItemsLayout(); + } + + QListViewLayoutInfo info; + info.bounds = layoutBounds; + info.grid = gridSize(); + info.spacing = (info.grid.isValid() ? 0 : spacing()); + info.first = first; + info.last = last; + info.wrap = isWrapping(); + info.flow = flow; + info.max = max; + + if (viewMode == QListView::ListMode) + return staticListView->doBatchedItemLayout(info, max); + return dynamicListView->doBatchedItemLayout(info, max); +} + +QListViewItem QListViewPrivate::indexToListViewItem(const QModelIndex &index) const +{ + if (!index.isValid() || isHidden(index.row())) + return QListViewItem(); + + if (viewMode == QListView::ListMode) + return staticListView->indexToListViewItem(index); + return dynamicListView->indexToListViewItem(index); +} + + +int QListViewPrivate::itemIndex(const QListViewItem &item) const +{ + if (viewMode == QListView::ListMode) + return staticListView->itemIndex(item); + return dynamicListView->itemIndex(item); +} + +QRect QListViewPrivate::mapToViewport(const QRect &rect, bool greedy) const +{ + Q_Q(const QListView); + if (!rect.isValid()) + return rect; + + QRect result = rect; + if (greedy) + result = staticListView->mapToViewport(rect); + + int dx = -q->horizontalOffset(); + int dy = -q->verticalOffset(); + result.adjust(dx, dy, dx, dy); + return result; +} + +QModelIndex QListViewPrivate::closestIndex(const QRect &target, + const QVector<QModelIndex> &candidates) const +{ + int distance = 0; + int shortest = INT_MAX; + QModelIndex closest; + QVector<QModelIndex>::const_iterator it = candidates.begin(); + + for (; it != candidates.end(); ++it) { + if (!(*it).isValid()) + continue; + + const QRect indexRect = indexToListViewItem(*it).rect(); + + //if the center x (or y) position of an item is included in the rect of the other item, + //we define the distance between them as the difference in x (or y) of their respective center. + // Otherwise, we use the nahattan length between the 2 items + if ((target.center().x() >= indexRect.x() && target.center().x() < indexRect.right()) + || (indexRect.center().x() >= target.x() && indexRect.center().x() < target.right())) { + //one item's center is at the vertical of the other + distance = qAbs(indexRect.center().y() - target.center().y()); + } else if ((target.center().y() >= indexRect.y() && target.center().y() < indexRect.bottom()) + || (indexRect.center().y() >= target.y() && indexRect.center().y() < target.bottom())) { + //one item's center is at the vertical of the other + distance = qAbs(indexRect.center().x() - target.center().x()); + } else { + distance = (indexRect.center() - target.center()).manhattanLength(); + } + if (distance < shortest) { + shortest = distance; + closest = *it; + } + } + return closest; +} + +QSize QListViewPrivate::itemSize(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + if (!uniformItemSizes) { + const QAbstractItemDelegate *delegate = delegateForIndex(index); + return delegate ? delegate->sizeHint(option, index) : QSize(); + } + if (!cachedItemSize.isValid()) { // the last item is probaly the largest, so we use its size + int row = model->rowCount(root) - 1; + QModelIndex sample = model->index(row, column, root); + const QAbstractItemDelegate *delegate = delegateForIndex(sample); + cachedItemSize = delegate ? delegate->sizeHint(option, sample) : QSize(); + } + return cachedItemSize; +} + +QItemSelection QListViewPrivate::selection(const QRect &rect) const +{ + QItemSelection selection; + QModelIndex tl, br; + intersectingSet(rect); + QVector<QModelIndex>::iterator it = intersectVector.begin(); + for (; it != intersectVector.end(); ++it) { + if (!tl.isValid() && !br.isValid()) { + tl = br = *it; + } else if ((*it).row() == (tl.row() - 1)) { + tl = *it; // expand current range + } else if ((*it).row() == (br.row() + 1)) { + br = (*it); // expand current range + } else { + selection.select(tl, br); // select current range + tl = br = *it; // start new range + } + } + + if (tl.isValid() && br.isValid()) + selection.select(tl, br); + else if (tl.isValid()) + selection.select(tl, tl); + else if (br.isValid()) + selection.select(br, br); + + return selection; +} + +/* + * Static ListView Implementation +*/ + +int QStaticListViewBase::verticalPerItemValue(int itemIndex, int verticalValue, int areaHeight, + bool above, bool below, bool wrap, + QListView::ScrollHint hint, int itemHeight) const +{ + int value = qBound(0, verticalValue, flowPositions.count() - 1); + if (above) + return perItemScrollToValue(itemIndex, value, areaHeight, QListView::PositionAtTop, + Qt::Vertical,wrap, itemHeight); + else if (below) + return perItemScrollToValue(itemIndex, value, areaHeight, QListView::PositionAtBottom, + Qt::Vertical, wrap, itemHeight); + else if (hint != QListView::EnsureVisible) + return perItemScrollToValue(itemIndex, value, areaHeight, hint, Qt::Vertical, wrap, itemHeight); + return value; +} + +int QStaticListViewBase::horizontalPerItemValue(int itemIndex, int horizontalValue, int areaWidth, + bool leftOf, bool rightOf, bool wrap, + QListView::ScrollHint hint, int itemWidth) const +{ + int value = qBound(0, horizontalValue, flowPositions.count() - 1); + if (leftOf) + return perItemScrollToValue(itemIndex, value, areaWidth, QListView::PositionAtTop, + Qt::Horizontal, wrap, itemWidth); + else if (rightOf) + return perItemScrollToValue(itemIndex, value, areaWidth, QListView::PositionAtBottom, + Qt::Horizontal, wrap, itemWidth); + else if (hint != QListView::EnsureVisible) + return perItemScrollToValue(itemIndex, value, areaWidth, hint, Qt::Horizontal, wrap, itemWidth); + return value; +} + +void QStaticListViewBase::scrollContentsBy(int &dx, int &dy) +{ + // ### reorder this logic + const int verticalValue = verticalScrollBarValue(); + const int horizontalValue = horizontalScrollBarValue(); + const bool vertical = (verticalScrollMode() == QAbstractItemView::ScrollPerItem); + const bool horizontal = (horizontalScrollMode() == QAbstractItemView::ScrollPerItem); + + if (isWrapping()) { + if (segmentPositions.isEmpty()) + return; + const int max = segmentPositions.count() - 1; + if (horizontal && flow() == QListView::TopToBottom && dx != 0) { + int currentValue = qBound(0, horizontalValue, max); + int previousValue = qBound(0, currentValue + dx, max); + int currentCoordinate = segmentPositions.at(currentValue); + int previousCoordinate = segmentPositions.at(previousValue); + dx = previousCoordinate - currentCoordinate; + } else if (vertical && flow() == QListView::LeftToRight && dy != 0) { + int currentValue = qBound(0, verticalValue, max); + int previousValue = qBound(0, currentValue + dy, max); + int currentCoordinate = segmentPositions.at(currentValue); + int previousCoordinate = segmentPositions.at(previousValue); + dy = previousCoordinate - currentCoordinate; + } + } else { + if (flowPositions.isEmpty()) + return; + const int max = flowPositions.count() - 1; + if (vertical && flow() == QListView::TopToBottom && dy != 0) { + int currentValue = qBound(0, verticalValue, max); + int previousValue = qBound(0, currentValue + dy, max); + int currentCoordinate = flowPositions.at(currentValue); + int previousCoordinate = flowPositions.at(previousValue); + dy = previousCoordinate - currentCoordinate; + } else if (horizontal && flow() == QListView::LeftToRight && dx != 0) { + int currentValue = qBound(0, horizontalValue, max); + int previousValue = qBound(0, currentValue + dx, max); + int currentCoordinate = flowPositions.at(currentValue); + int previousCoordinate = flowPositions.at(previousValue); + dx = previousCoordinate - currentCoordinate; + } + } +} + +bool QStaticListViewBase::doBatchedItemLayout(const QListViewLayoutInfo &info, int max) +{ + doStaticLayout(info); + if (batchStartRow > max) { // stop items layout + flowPositions.resize(flowPositions.count()); + segmentPositions.resize(segmentPositions.count()); + segmentStartRows.resize(segmentStartRows.count()); + return true; // done + } + return false; // not done +} + +QListViewItem QStaticListViewBase::indexToListViewItem(const QModelIndex &index) const +{ + if (flowPositions.isEmpty() + || segmentPositions.isEmpty() + || index.row() > flowPositions.count()) + return QListViewItem(); + + const int segment = qBinarySearch<int>(segmentStartRows, index.row(), + 0, segmentStartRows.count() - 1); + + + QStyleOptionViewItemV4 options = viewOptions(); + options.rect.setSize(contentsSize); + QSize size = (uniformItemSizes() && cachedItemSize().isValid()) + ? cachedItemSize() : itemSize(options, index); + + QPoint pos; + if (flow() == QListView::LeftToRight) { + pos.setX(flowPositions.at(index.row())); + pos.setY(segmentPositions.at(segment)); + } else { // TopToBottom + pos.setY(flowPositions.at(index.row())); + pos.setX(segmentPositions.at(segment)); + if (isWrapping()) { // make the items as wide as the segment + int right = (segment + 1 >= segmentPositions.count() + ? contentsSize.width() + : segmentPositions.at(segment + 1)); + size.setWidth(right - pos.x()); + } else { // make the items as wide as the viewport + size.setWidth(qMax(size.width(), viewport()->width())); + } + } + + return QListViewItem(QRect(pos, size), index.row()); +} + +QPoint QStaticListViewBase::initStaticLayout(const QListViewLayoutInfo &info) +{ + int x, y; + if (info.first == 0) { + flowPositions.clear(); + segmentPositions.clear(); + segmentStartRows.clear(); + segmentExtents.clear(); + x = info.bounds.left() + info.spacing; + y = info.bounds.top() + info.spacing; + segmentPositions.append(info.flow == QListView::LeftToRight ? y : x); + segmentStartRows.append(0); + } else if (info.wrap) { + if (info.flow == QListView::LeftToRight) { + x = batchSavedPosition; + y = segmentPositions.last(); + } else { // flow == QListView::TopToBottom + x = segmentPositions.last(); + y = batchSavedPosition; + } + } else { // not first and not wrap + if (info.flow == QListView::LeftToRight) { + x = batchSavedPosition; + y = info.bounds.top() + info.spacing; + } else { // flow == QListView::TopToBottom + x = info.bounds.left() + info.spacing; + y = batchSavedPosition; + } + } + return QPoint(x, y); +} + +/*! + \internal +*/ +void QStaticListViewBase::doStaticLayout(const QListViewLayoutInfo &info) +{ + const bool useItemSize = !info.grid.isValid(); + const QPoint topLeft = initStaticLayout(info); + QStyleOptionViewItemV4 option = viewOptions(); + option.rect = info.bounds; + + // The static layout data structures are as follows: + // One vector contains the coordinate in the direction of layout flow. + // Another vector contains the coordinates of the segments. + // A third vector contains the index (model row) of the first item + // of each segment. + + int segStartPosition; + int segEndPosition; + int deltaFlowPosition; + int deltaSegPosition; + int deltaSegHint; + int flowPosition; + int segPosition; + + if (info.flow == QListView::LeftToRight) { + segStartPosition = info.bounds.left(); + segEndPosition = info.bounds.width(); + flowPosition = topLeft.x(); + segPosition = topLeft.y(); + deltaFlowPosition = info.grid.width(); // dx + deltaSegPosition = useItemSize ? batchSavedDeltaSeg : info.grid.height(); // dy + deltaSegHint = info.grid.height(); + } else { // flow == QListView::TopToBottom + segStartPosition = info.bounds.top(); + segEndPosition = info.bounds.height(); + flowPosition = topLeft.y(); + segPosition = topLeft.x(); + deltaFlowPosition = info.grid.height(); // dy + deltaSegPosition = useItemSize ? batchSavedDeltaSeg : info.grid.width(); // dx + deltaSegHint = info.grid.width(); + } + + for (int row = info.first; row <= info.last; ++row) { + if (isHidden(row)) { // ### + flowPositions.append(flowPosition); + } else { + // if we are not using a grid, we need to find the deltas + if (useItemSize) { + QSize hint = itemSize(option, modelIndex(row)); + if (info.flow == QListView::LeftToRight) { + deltaFlowPosition = hint.width() + info.spacing; + deltaSegHint = hint.height() + info.spacing; + } else { // TopToBottom + deltaFlowPosition = hint.height() + info.spacing; + deltaSegHint = hint.width() + info.spacing; + } + } + // create new segment + if (info.wrap && (flowPosition + deltaFlowPosition >= segEndPosition)) { + segmentExtents.append(flowPosition); + flowPosition = info.spacing + segStartPosition; + segPosition += deltaSegPosition; + segmentPositions.append(segPosition); + segmentStartRows.append(row); + deltaSegPosition = 0; + } + // save the flow position of this item + flowPositions.append(flowPosition); + // prepare for the next item + deltaSegPosition = qMax(deltaSegHint, deltaSegPosition); + flowPosition += info.spacing + deltaFlowPosition; + } + } + // used when laying out next batch + batchSavedPosition = flowPosition; + batchSavedDeltaSeg = deltaSegPosition; + batchStartRow = info.last + 1; + if (info.last == info.max) + flowPosition -= info.spacing; // remove extra spacing + // set the contents size + QRect rect = info.bounds; + if (info.flow == QListView::LeftToRight) { + rect.setRight(segmentPositions.count() == 1 ? flowPosition : info.bounds.right()); + rect.setBottom(segPosition + deltaSegPosition); + } else { // TopToBottom + rect.setRight(segPosition + deltaSegPosition); + rect.setBottom(segmentPositions.count() == 1 ? flowPosition : info.bounds.bottom()); + } + contentsSize = QSize(rect.right(), rect.bottom()); + // if it is the last batch, save the end of the segments + if (info.last == info.max) { + segmentExtents.append(flowPosition); + flowPositions.append(flowPosition); + segmentPositions.append(info.wrap ? segPosition + deltaSegPosition : INT_MAX); + } + // if the new items are visble, update the viewport + QRect changedRect(topLeft, rect.bottomRight()); + if (clipRect().intersects(changedRect)) + viewport()->update(); +} + +/*! + \internal + Finds the set of items intersecting with \a area. + In this function, itemsize is counted from topleft to the start of the next item. +*/ +void QStaticListViewBase::intersectingStaticSet(const QRect &area) const +{ + clearIntersections(); + int segStartPosition; + int segEndPosition; + int flowStartPosition; + int flowEndPosition; + if (flow() == QListView::LeftToRight) { + segStartPosition = area.top(); + segEndPosition = area.bottom(); + flowStartPosition = area.left(); + flowEndPosition = area.right(); + } else { + segStartPosition = area.left(); + segEndPosition = area.right(); + flowStartPosition = area.top(); + flowEndPosition = area.bottom(); + } + if (segmentPositions.count() < 2 || flowPositions.isEmpty()) + return; + // the last segment position is actually the edge of the last segment + const int segLast = segmentPositions.count() - 2; + int seg = qBinarySearch<int>(segmentPositions, segStartPosition, 0, segLast + 1); + for (; seg <= segLast && segmentPositions.at(seg) <= segEndPosition; ++seg) { + int first = segmentStartRows.at(seg); + int last = (seg < segLast ? segmentStartRows.at(seg + 1) : batchStartRow) - 1; + if (segmentExtents.at(seg) < flowStartPosition) + continue; + int row = qBinarySearch<int>(flowPositions, flowStartPosition, first, last); + for (; row <= last && flowPositions.at(row) <= flowEndPosition; ++row) { + if (isHidden(row)) + continue; + QModelIndex index = modelIndex(row); + if (index.isValid()) + appendToIntersections(index); +#if 0 // for debugging + else + qWarning("intersectingStaticSet: row %d was invalid", row); +#endif + } + } +} + +int QStaticListViewBase::itemIndex(const QListViewItem &item) const +{ + return item.indexHint; +} + +QRect QStaticListViewBase::mapToViewport(const QRect &rect) const +{ + if (isWrapping()) + return rect; + // If the listview is in "listbox-mode", the items are as wide as the view. + QRect result = rect; + QSize vsize = viewport()->size(); + QSize csize = contentsSize; + if (flow() == QListView::TopToBottom) { + result.setLeft(spacing()); + result.setWidth(qMax(csize.width(), vsize.width()) - 2 * spacing()); + } else { // LeftToRight + result.setTop(spacing()); + result.setHeight(qMax(csize.height(), vsize.height()) - 2 * spacing()); + } + return result; +} + +int QStaticListViewBase::perItemScrollingPageSteps(int length, int bounds, bool wrap) const +{ + const QVector<int> positions = (wrap ? segmentPositions : flowPositions); + if (positions.isEmpty() || bounds <= length) + return positions.count(); + if (uniformItemSizes()) { + for (int i = 1; i < positions.count(); ++i) + if (positions.at(i) > 0) + return length / positions.at(i); + return 0; // all items had height 0 + } + int pageSteps = 0; + int steps = positions.count() - 1; + int max = qMax(length, bounds); + int min = qMin(length, bounds); + int pos = min - (max - positions.last()); + + while (pos >= 0 && steps > 0) { + pos -= (positions.at(steps) - positions.at(steps - 1)); + if (pos >= 0) //this item should be visible + ++pageSteps; + --steps; + } + + // at this point we know that positions has at least one entry + return qMax(pageSteps, 1); +} + +int QStaticListViewBase::perItemScrollToValue(int index, int scrollValue, int viewportSize, + QAbstractItemView::ScrollHint hint, + Qt::Orientation orientation, bool wrap, int itemExtent) const +{ + if (index < 0) + return scrollValue; + if (!wrap) { + int topIndex = index; + const int bottomIndex = topIndex; + const int bottomCoordinate = flowPositions.at(index); + + while (topIndex > 0 && + (bottomCoordinate - flowPositions.at(topIndex-1) + itemExtent) <= (viewportSize)) { + topIndex--; + } + + const int itemCount = bottomIndex - topIndex + 1; + switch (hint) { + case QAbstractItemView::PositionAtTop: + return index; + case QAbstractItemView::PositionAtBottom: + return index - itemCount + 1; + case QAbstractItemView::PositionAtCenter: + return index - (itemCount / 2); + default: + break; + } + } else { // wrapping + Qt::Orientation flowOrientation = (flow() == QListView::LeftToRight + ? Qt::Horizontal : Qt::Vertical); + if (flowOrientation == orientation) { // scrolling in the "flow" direction + // ### wrapped scrolling in the flow direction + return flowPositions.at(index); // ### always pixel based for now + } else if (!segmentStartRows.isEmpty()) { // we are scrolling in the "segment" direction + int segment = qBinarySearch<int>(segmentStartRows, index, 0, segmentStartRows.count() - 1); + int leftSegment = segment; + const int rightSegment = leftSegment; + const int bottomCoordinate = segmentPositions.at(segment); + + while (leftSegment > scrollValue && + (bottomCoordinate - segmentPositions.at(leftSegment-1) + itemExtent) <= (viewportSize)) { + leftSegment--; + } + + const int segmentCount = rightSegment - leftSegment + 1; + switch (hint) { + case QAbstractItemView::PositionAtTop: + return segment; + case QAbstractItemView::PositionAtBottom: + return segment - segmentCount + 1; + case QAbstractItemView::PositionAtCenter: + return segment - (segmentCount / 2); + default: + break; + } + } + } + return scrollValue; +} + +void QStaticListViewBase::clear() +{ + flowPositions.clear(); + segmentPositions.clear(); + segmentStartRows.clear(); + segmentExtents.clear(); + batchSavedPosition = 0; + batchStartRow = 0; + batchSavedDeltaSeg = 0; +} + +/* + * Dynamic ListView Implementation +*/ + +void QDynamicListViewBase::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + if (column() >= topLeft.column() && column() <= bottomRight.column()) { + QStyleOptionViewItemV4 option = viewOptions(); + int bottom = qMin(items.count(), bottomRight.row() + 1); + for (int row = topLeft.row(); row < bottom; ++row) + items[row].resize(itemSize(option, modelIndex(row))); + } +} + +bool QDynamicListViewBase::doBatchedItemLayout(const QListViewLayoutInfo &info, int max) +{ + if (info.last >= items.count()) { + createItems(info.last + 1); + doDynamicLayout(info); + } + return (batchStartRow > max); // done +} + +QListViewItem QDynamicListViewBase::indexToListViewItem(const QModelIndex &index) const +{ + if (index.isValid() && index.row() < items.count()) + return items.at(index.row()); + return QListViewItem(); +} + +void QDynamicListViewBase::initBspTree(const QSize &contents) +{ + // remove all items from the tree + int leafCount = tree.leafCount(); + for (int l = 0; l < leafCount; ++l) + tree.leaf(l).clear(); + // we have to get the bounding rect of the items before we can initialize the tree + QBspTree::Node::Type type = QBspTree::Node::Both; // 2D + // simple heuristics to get better bsp + if (contents.height() / contents.width() >= 3) + type = QBspTree::Node::HorizontalPlane; + else if (contents.width() / contents.height() >= 3) + type = QBspTree::Node::VerticalPlane; + // build tree for the bounding rect (not just the contents rect) + tree.init(QRect(0, 0, contents.width(), contents.height()), type); +} + +QPoint QDynamicListViewBase::initDynamicLayout(const QListViewLayoutInfo &info) +{ + int x, y; + if (info.first == 0) { + x = info.bounds.x() + info.spacing; + y = info.bounds.y() + info.spacing; + items.reserve(rowCount() - hiddenCount()); + } else { + const QListViewItem item = items.at(info.first - 1); + x = item.x; + y = item.y; + if (info.flow == QListView::LeftToRight) + x += (info.grid.isValid() ? info.grid.width() : item.w) + info.spacing; + else + y += (info.grid.isValid() ? info.grid.height() : item.h) + info.spacing; + } + return QPoint(x, y); +} + +/*! + \internal +*/ +void QDynamicListViewBase::doDynamicLayout(const QListViewLayoutInfo &info) +{ + const bool useItemSize = !info.grid.isValid(); + const QPoint topLeft = initDynamicLayout(info); + + int segStartPosition; + int segEndPosition; + int deltaFlowPosition; + int deltaSegPosition; + int deltaSegHint; + int flowPosition; + int segPosition; + + if (info.flow == QListView::LeftToRight) { + segStartPosition = info.bounds.left() + info.spacing; + segEndPosition = info.bounds.right(); + deltaFlowPosition = info.grid.width(); // dx + deltaSegPosition = (useItemSize ? batchSavedDeltaSeg : info.grid.height()); // dy + deltaSegHint = info.grid.height(); + flowPosition = topLeft.x(); + segPosition = topLeft.y(); + } else { // flow == QListView::TopToBottom + segStartPosition = info.bounds.top() + info.spacing; + segEndPosition = info.bounds.bottom(); + deltaFlowPosition = info.grid.height(); // dy + deltaSegPosition = (useItemSize ? batchSavedDeltaSeg : info.grid.width()); // dx + deltaSegHint = info.grid.width(); + flowPosition = topLeft.y(); + segPosition = topLeft.x(); + } + + if (moved.count() != items.count()) + moved.resize(items.count()); + + QRect rect(QPoint(0, 0), topLeft); + QListViewItem *item = 0; + for (int row = info.first; row <= info.last; ++row) { + item = &items[row]; + if (isHidden(row)) { + item->invalidate(); + } else { + // if we are not using a grid, we need to find the deltas + if (useItemSize) { + if (info.flow == QListView::LeftToRight) + deltaFlowPosition = item->w + info.spacing; + else + deltaFlowPosition = item->h + info.spacing; + } else { + item->w = qMin<int>(info.grid.width(), item->w); + item->h = qMin<int>(info.grid.height(), item->h); + } + + // create new segment + if (info.wrap + && flowPosition + deltaFlowPosition > segEndPosition + && flowPosition > segStartPosition) { + flowPosition = segStartPosition; + segPosition += deltaSegPosition; + if (useItemSize) + deltaSegPosition = 0; + } + // We must delay calculation of the seg adjustment, as this item + // may have caused a wrap to occur + if (useItemSize) { + if (info.flow == QListView::LeftToRight) + deltaSegHint = item->h + info.spacing; + else + deltaSegHint = item->w + info.spacing; + deltaSegPosition = qMax(deltaSegPosition, deltaSegHint); + } + + // set the position of the item + // ### idealy we should have some sort of alignment hint for the item + // ### (normally that would be a point between the icon and the text) + if (!moved.testBit(row)) { + if (info.flow == QListView::LeftToRight) { + if (useItemSize) { + item->x = flowPosition; + item->y = segPosition; + } else { // use grid + item->x = flowPosition + ((deltaFlowPosition - item->w) / 2); + item->y = segPosition; + } + } else { // TopToBottom + if (useItemSize) { + item->y = flowPosition; + item->x = segPosition; + } else { // use grid + item->y = flowPosition + ((deltaFlowPosition - item->h) / 2); + item->x = segPosition; + } + } + } + + // let the contents contain the new item + if (useItemSize) + rect |= item->rect(); + else if (info.flow == QListView::LeftToRight) + rect |= QRect(flowPosition, segPosition, deltaFlowPosition, deltaSegPosition); + else // flow == TopToBottom + rect |= QRect(segPosition, flowPosition, deltaSegPosition, deltaFlowPosition); + + // prepare for next item + flowPosition += deltaFlowPosition; // current position + item width + gap + } + } + batchSavedDeltaSeg = deltaSegPosition; + batchStartRow = info.last + 1; + bool done = (info.last >= rowCount() - 1); + // resize the content area + if (done || !info.bounds.contains(item->rect())) + contentsSize = QSize(rect.width(), rect.height()); + // resize tree + int insertFrom = info.first; + if (done || info.first == 0) { + initBspTree(rect.size()); + insertFrom = 0; + } + // insert items in tree + for (int row = insertFrom; row <= info.last; ++row) + tree.insertLeaf(items.at(row).rect(), row); + // if the new items are visble, update the viewport + QRect changedRect(topLeft, rect.bottomRight()); + if (clipRect().intersects(changedRect)) + viewport()->update(); +} + +void QDynamicListViewBase::intersectingDynamicSet(const QRect &area) const +{ + clearIntersections(); + QListViewPrivate *that = const_cast<QListViewPrivate*>(dd); + QBspTree::Data data(static_cast<void*>(that)); + that->dynamicListView->tree.climbTree(area, &QDynamicListViewBase::addLeaf, data); +} + +void QDynamicListViewBase::createItems(int to) +{ + int count = items.count(); + QSize size; + QStyleOptionViewItemV4 option = viewOptions(); + for (int row = count; row < to; ++row) { + size = itemSize(option, modelIndex(row)); + QListViewItem item(QRect(0, 0, size.width(), size.height()), row); // default pos + items.append(item); + } +} + +void QDynamicListViewBase::drawItems(QPainter *painter, const QVector<QModelIndex> &indexes) const +{ + QStyleOptionViewItemV4 option = viewOptions(); + option.state &= ~QStyle::State_MouseOver; + QVector<QModelIndex>::const_iterator it = indexes.begin(); + QListViewItem item = indexToListViewItem(*it); + for (; it != indexes.end(); ++it) { + item = indexToListViewItem(*it); + option.rect = viewItemRect(item); + delegate(*it)->paint(painter, option, *it); + } +} + +QRect QDynamicListViewBase::itemsRect(const QVector<QModelIndex> &indexes) const +{ + QVector<QModelIndex>::const_iterator it = indexes.begin(); + QListViewItem item = indexToListViewItem(*it); + QRect rect(item.x, item.y, item.w, item.h); + for (; it != indexes.end(); ++it) { + item = indexToListViewItem(*it); + rect |= viewItemRect(item); + } + return rect; +} + +int QDynamicListViewBase::itemIndex(const QListViewItem &item) const +{ + if (!item.isValid()) + return -1; + int i = item.indexHint; + if (i < items.count()) { + if (items.at(i) == item) + return i; + } else { + i = items.count() - 1; + } + + int j = i; + int c = items.count(); + bool a = true; + bool b = true; + + while (a || b) { + if (a) { + if (items.at(i) == item) { + items.at(i).indexHint = i; + return i; + } + a = ++i < c; + } + if (b) { + if (items.at(j) == item) { + items.at(j).indexHint = j; + return j; + } + b = --j > -1; + } + } + return -1; +} + +void QDynamicListViewBase::addLeaf(QVector<int> &leaf, const QRect &area, + uint visited, QBspTree::Data data) +{ + QListViewItem *vi; + QListViewPrivate *_this = static_cast<QListViewPrivate *>(data.ptr); + for (int i = 0; i < leaf.count(); ++i) { + int idx = leaf.at(i); + if (idx < 0 || idx >= _this->dynamicListView->items.count()) + continue; + vi = &_this->dynamicListView->items[idx]; + Q_ASSERT(vi); + if (vi->rect().intersects(area) && vi->visited != visited) { + QModelIndex index = _this->listViewItemToIndex(*vi); + Q_ASSERT(index.isValid()); + _this->intersectVector.append(index); + vi->visited = visited; + } + } +} + +void QDynamicListViewBase::insertItem(int index) +{ + if (index >= 0 && index < items.count()) + tree.insertLeaf(items.at(index).rect(), index); +} + +void QDynamicListViewBase::removeItem(int index) +{ + if (index >= 0 && index < items.count()) + tree.removeLeaf(items.at(index).rect(), index); +} + +void QDynamicListViewBase::moveItem(int index, const QPoint &dest) +{ + // does not impact on the bintree itself or the contents rect + QListViewItem *item = &items[index]; + QRect rect = item->rect(); + + // move the item without removing it from the tree + tree.removeLeaf(rect, index); + item->move(dest); + tree.insertLeaf(QRect(dest, rect.size()), index); + + // resize the contents area + contentsSize = (QRect(QPoint(0, 0), contentsSize)|QRect(dest, rect.size())).size(); + + // mark the item as moved + if (moved.count() != items.count()) + moved.resize(items.count()); + moved.setBit(index, true); +} + +QPoint QDynamicListViewBase::snapToGrid(const QPoint &pos) const +{ + int x = pos.x() - (pos.x() % gridSize().width()); + int y = pos.y() - (pos.y() % gridSize().height()); + return QPoint(x, y); +} + +QPoint QDynamicListViewBase::draggedItemsDelta() const +{ + if (movement() == QListView::Snap) { + QPoint snapdelta = QPoint((offset().x() % gridSize().width()), + (offset().y() % gridSize().height())); + return snapToGrid(draggedItemsPos + snapdelta) - snapToGrid(pressedPosition()) - snapdelta; + } + return draggedItemsPos - pressedPosition(); +} + +QRect QDynamicListViewBase::draggedItemsRect() const +{ + QRect rect = itemsRect(draggedItems); + rect.translate(draggedItemsDelta()); + return rect; +} + +void QListViewPrivate::scrollElasticBandBy(int dx, int dy) +{ + if (dx > 0) // right + elasticBand.moveRight(elasticBand.right() + dx); + else if (dx < 0) // left + elasticBand.moveLeft(elasticBand.left() - dx); + if (dy > 0) // down + elasticBand.moveBottom(elasticBand.bottom() + dy); + else if (dy < 0) // up + elasticBand.moveTop(elasticBand.top() - dy); +} + +void QDynamicListViewBase::clear() +{ + tree.destroy(); + items.clear(); + moved.clear(); + batchStartRow = 0; + batchSavedDeltaSeg = 0; +} + +void QDynamicListViewBase::updateContentsSize() +{ + QRect bounding; + for (int i = 0; i < items.count(); ++i) + bounding |= items.at(i).rect(); + contentsSize = bounding.size(); +} + +/*! + \reimp +*/ +void QListView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ +#ifndef QT_NO_ACCESSIBILITY + if (QAccessible::isActive()) { + if (current.isValid()) { + int entry = visualIndex(current) + 1; + QAccessible::updateAccessibility(viewport(), entry, QAccessible::Focus); + } + } +#endif + QAbstractItemView::currentChanged(current, previous); +} + +/*! + \reimp +*/ +void QListView::selectionChanged(const QItemSelection &selected, + const QItemSelection &deselected) +{ +#ifndef QT_NO_ACCESSIBILITY + if (QAccessible::isActive()) { + // ### does not work properly for selection ranges. + QModelIndex sel = selected.indexes().value(0); + if (sel.isValid()) { + int entry = visualIndex(sel) + 1; + QAccessible::updateAccessibility(viewport(), entry, QAccessible::Selection); + } + QModelIndex desel = deselected.indexes().value(0); + if (desel.isValid()) { + int entry = visualIndex(desel) + 1; + QAccessible::updateAccessibility(viewport(), entry, QAccessible::SelectionRemove); + } + } +#endif + QAbstractItemView::selectionChanged(selected, deselected); +} + +int QListView::visualIndex(const QModelIndex &index) const +{ + Q_D(const QListView); + d->executePostedLayout(); + QListViewItem itm = d->indexToListViewItem(index); + return d->itemIndex(itm); +} + +QT_END_NAMESPACE + +#endif // QT_NO_LISTVIEW diff --git a/src/gui/itemviews/qlistview.h b/src/gui/itemviews/qlistview.h new file mode 100644 index 0000000..dd719d9 --- /dev/null +++ b/src/gui/itemviews/qlistview.h @@ -0,0 +1,203 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLISTVIEW_H +#define QLISTVIEW_H + +#include <QtGui/qabstractitemview.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_LISTVIEW + +class QListViewPrivate; + +class Q_GUI_EXPORT QListView : public QAbstractItemView +{ + Q_OBJECT + Q_ENUMS(Movement Flow ResizeMode LayoutMode ViewMode) + Q_PROPERTY(Movement movement READ movement WRITE setMovement) + Q_PROPERTY(Flow flow READ flow WRITE setFlow) + Q_PROPERTY(bool isWrapping READ isWrapping WRITE setWrapping) + Q_PROPERTY(ResizeMode resizeMode READ resizeMode WRITE setResizeMode) + Q_PROPERTY(LayoutMode layoutMode READ layoutMode WRITE setLayoutMode) + Q_PROPERTY(int spacing READ spacing WRITE setSpacing) + Q_PROPERTY(QSize gridSize READ gridSize WRITE setGridSize) + Q_PROPERTY(ViewMode viewMode READ viewMode WRITE setViewMode) + Q_PROPERTY(int modelColumn READ modelColumn WRITE setModelColumn) + Q_PROPERTY(bool uniformItemSizes READ uniformItemSizes WRITE setUniformItemSizes) + Q_PROPERTY(int batchSize READ batchSize WRITE setBatchSize) + Q_PROPERTY(bool wordWrap READ wordWrap WRITE setWordWrap) + Q_PROPERTY(bool selectionRectVisible READ isSelectionRectVisible WRITE setSelectionRectVisible) + +public: + enum Movement { Static, Free, Snap }; + enum Flow { LeftToRight, TopToBottom }; + enum ResizeMode { Fixed, Adjust }; + enum LayoutMode { SinglePass, Batched }; + enum ViewMode { ListMode, IconMode }; + + explicit QListView(QWidget *parent = 0); + ~QListView(); + + void setMovement(Movement movement); + Movement movement() const; + + void setFlow(Flow flow); + Flow flow() const; + + void setWrapping(bool enable); + bool isWrapping() const; + + void setResizeMode(ResizeMode mode); + ResizeMode resizeMode() const; + + void setLayoutMode(LayoutMode mode); + LayoutMode layoutMode() const; + + void setSpacing(int space); + int spacing() const; + + void setBatchSize(int batchSize); + int batchSize() const; + + void setGridSize(const QSize &size); + QSize gridSize() const; + + void setViewMode(ViewMode mode); + ViewMode viewMode() const; + + void clearPropertyFlags(); + + bool isRowHidden(int row) const; + void setRowHidden(int row, bool hide); + + void setModelColumn(int column); + int modelColumn() const; + + void setUniformItemSizes(bool enable); + bool uniformItemSizes() const; + + void setWordWrap(bool on); + bool wordWrap() const; + + void setSelectionRectVisible(bool show); + bool isSelectionRectVisible() const; + + QRect visualRect(const QModelIndex &index) const; + void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible); + QModelIndex indexAt(const QPoint &p) const; + + void doItemsLayout(); + void reset(); + void setRootIndex(const QModelIndex &index); + +Q_SIGNALS: + void indexesMoved(const QModelIndexList &indexes); + +protected: + QListView(QListViewPrivate &, QWidget *parent = 0); + + bool event(QEvent *e); + + void scrollContentsBy(int dx, int dy); + + void resizeContents(int width, int height); + QSize contentsSize() const; + + void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void rowsInserted(const QModelIndex &parent, int start, int end); + void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + + void mouseMoveEvent(QMouseEvent *e); + void mouseReleaseEvent(QMouseEvent *e); + + void timerEvent(QTimerEvent *e); + void resizeEvent(QResizeEvent *e); +#ifndef QT_NO_DRAGANDDROP + void dragMoveEvent(QDragMoveEvent *e); + void dragLeaveEvent(QDragLeaveEvent *e); + void dropEvent(QDropEvent *e); + void startDrag(Qt::DropActions supportedActions); + + void internalDrop(QDropEvent *e); + void internalDrag(Qt::DropActions supportedActions); +#endif // QT_NO_DRAGANDDROP + + QStyleOptionViewItem viewOptions() const; + void paintEvent(QPaintEvent *e); + + int horizontalOffset() const; + int verticalOffset() const; + QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers); + QRect rectForIndex(const QModelIndex &index) const; + void setPositionForIndex(const QPoint &position, const QModelIndex &index); + + void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command); + QRegion visualRegionForSelection(const QItemSelection &selection) const; + QModelIndexList selectedIndexes() const; + + void updateGeometries(); + + bool isIndexHidden(const QModelIndex &index) const; + + void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); + +private: + friend class QAccessibleItemView; + int visualIndex(const QModelIndex &index) const; + + Q_DECLARE_PRIVATE(QListView) + Q_DISABLE_COPY(QListView) +}; + +#endif // QT_NO_LISTVIEW + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QLISTVIEW_H diff --git a/src/gui/itemviews/qlistview_p.h b/src/gui/itemviews/qlistview_p.h new file mode 100644 index 0000000..4568d8c --- /dev/null +++ b/src/gui/itemviews/qlistview_p.h @@ -0,0 +1,450 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLISTVIEW_P_H +#define QLISTVIEW_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qabstractitemview_p.h" +#include "qrubberband.h" +#include "qbitarray.h" +#include "qbsptree_p.h" +#include <limits.h> +#include <qscrollbar.h> + +#ifndef QT_NO_LISTVIEW + +QT_BEGIN_NAMESPACE + +class QListViewItem +{ + friend class QListViewPrivate; + friend class QStaticListViewBase; + friend class QDynamicListViewBase; +public: + inline QListViewItem() + : x(-1), y(-1), w(0), h(0), indexHint(-1), visited(0xffff) {} + inline QListViewItem(const QListViewItem &other) + : x(other.x), y(other.y), w(other.w), h(other.h), + indexHint(other.indexHint), visited(other.visited) {} + inline QListViewItem(QRect r, int i) + : x(r.x()), y(r.y()), w(qMin(r.width(), SHRT_MAX)), h(qMin(r.height(), SHRT_MAX)), + indexHint(i), visited(0xffff) {} + inline bool operator==(const QListViewItem &other) const { + return (x == other.x && y == other.y && w == other.w && h == other.h && + indexHint == other.indexHint); } + inline bool operator!=(const QListViewItem &other) const + { return !(*this == other); } + inline bool isValid() const + { return (x > -1) && (y > -1) && (w > 0) && (h > 0) && (indexHint > -1); } + inline void invalidate() + { x = -1; y = -1; w = 0; h = 0; } + inline void resize(const QSize &size) + { w = qMin(size.width(), SHRT_MAX); h = qMin(size.height(), SHRT_MAX); } + inline void move(const QPoint &position) + { x = position.x(); y = position.y(); } + inline int width() const { return w; } + inline int height() const { return h; } +private: + inline QRect rect() const + { return QRect(x, y, w, h); } + int x, y; + short w, h; + mutable int indexHint; + uint visited; +}; + +struct QListViewLayoutInfo +{ + QRect bounds; + QSize grid; + int spacing; + int first; + int last; + bool wrap; + QListView::Flow flow; + int max; +}; + +class QListView; +class QListViewPrivate; + +class QCommonListViewBase +{ +public: + inline QCommonListViewBase(QListView *q, QListViewPrivate *d) : dd(d), qq(q) {} + + inline int spacing() const; + inline bool isWrapping() const; + inline QSize gridSize() const; + inline QListView::Flow flow() const; + inline QListView::Movement movement() const; + + inline QPoint offset() const; + inline QPoint pressedPosition() const; + inline bool uniformItemSizes() const; + inline int column() const; + + inline int verticalScrollBarValue() const; + inline int horizontalScrollBarValue() const; + inline QListView::ScrollMode verticalScrollMode() const; + inline QListView::ScrollMode horizontalScrollMode() const; + + inline QModelIndex modelIndex(int row) const; + inline int rowCount() const; + + inline QStyleOptionViewItemV4 viewOptions() const; + inline QWidget *viewport() const; + inline QRect clipRect() const; + + inline QSize cachedItemSize() const; + inline QRect viewItemRect(const QListViewItem &item) const; + inline QSize itemSize(const QStyleOptionViewItemV2 &opt, const QModelIndex &idx) const; + inline QAbstractItemDelegate *delegate(const QModelIndex &idx) const; + + inline bool isHidden(int row) const; + inline int hiddenCount() const; + + inline void clearIntersections() const; + inline void appendToIntersections(const QModelIndex &idx) const; + + inline bool isRightToLeft() const; + + QListViewPrivate *dd; + QListView *qq; +}; + +// ### rename to QListModeViewBase +class QStaticListViewBase : public QCommonListViewBase +{ + friend class QListViewPrivate; +public: + QStaticListViewBase(QListView *q, QListViewPrivate *d) : QCommonListViewBase(q, d), + batchStartRow(0), batchSavedDeltaSeg(0), batchSavedPosition(0) {} + + QVector<int> flowPositions; + QVector<int> segmentPositions; + QVector<int> segmentStartRows; + QVector<int> segmentExtents; + + QSize contentsSize; + + // used when laying out in batches + int batchStartRow; + int batchSavedDeltaSeg; + int batchSavedPosition; + + bool doBatchedItemLayout(const QListViewLayoutInfo &info, int max); + + QPoint initStaticLayout(const QListViewLayoutInfo &info); + void doStaticLayout(const QListViewLayoutInfo &info); + void intersectingStaticSet(const QRect &area) const; + + int itemIndex(const QListViewItem &item) const; + + int perItemScrollingPageSteps(int length, int bounds, bool wrap) const; + + int perItemScrollToValue(int index, int value, int height, + QAbstractItemView::ScrollHint hint, + Qt::Orientation orientation, bool wrap, int extent) const; + + QRect mapToViewport(const QRect &rect) const; + + QListViewItem indexToListViewItem(const QModelIndex &index) const; + + void scrollContentsBy(int &dx, int &dy); + + int verticalPerItemValue(int itemIndex, int verticalValue, int areaHeight, + bool above, bool below, bool wrap, QListView::ScrollHint hint, int itemHeight) const; + int horizontalPerItemValue(int itemIndex, int horizontalValue, int areaWidth, + bool leftOf, bool rightOf, bool wrap, QListView::ScrollHint hint, int itemWidth) const; + + void clear(); +}; + +// ### rename to QIconModeViewBase +class QDynamicListViewBase : public QCommonListViewBase +{ + friend class QListViewPrivate; +public: + QDynamicListViewBase(QListView *q, QListViewPrivate *d) : QCommonListViewBase(q, d), + batchStartRow(0), batchSavedDeltaSeg(0) {} + + QBspTree tree; + QVector<QListViewItem> items; + QBitArray moved; + + QSize contentsSize; + + QVector<QModelIndex> draggedItems; // indices to the tree.itemVector + mutable QPoint draggedItemsPos; + + // used when laying out in batches + int batchStartRow; + int batchSavedDeltaSeg; + + void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + bool doBatchedItemLayout(const QListViewLayoutInfo &info, int max); + + void initBspTree(const QSize &contents); + QPoint initDynamicLayout(const QListViewLayoutInfo &info); + void doDynamicLayout(const QListViewLayoutInfo &info); + void intersectingDynamicSet(const QRect &area) const; + + static void addLeaf(QVector<int> &leaf, const QRect &area, + uint visited, QBspTree::Data data); + + void insertItem(int index); + void removeItem(int index); + void moveItem(int index, const QPoint &dest); + + int itemIndex(const QListViewItem &item) const; + + void createItems(int to); + void drawItems(QPainter *painter, const QVector<QModelIndex> &indexes) const; + QRect itemsRect(const QVector<QModelIndex> &indexes) const; + + QPoint draggedItemsDelta() const; + QRect draggedItemsRect() const; + + QPoint snapToGrid(const QPoint &pos) const; + + void scrollElasticBandBy(int dx, int dy); + + QListViewItem indexToListViewItem(const QModelIndex &index) const; + + void clear(); + void updateContentsSize(); +}; + +class QListViewPrivate: public QAbstractItemViewPrivate +{ + Q_DECLARE_PUBLIC(QListView) +public: + QListViewPrivate(); + ~QListViewPrivate(); + + void clear(); + void prepareItemsLayout(); + + bool doItemsLayout(int num); + + inline void intersectingSet(const QRect &area, bool doLayout = true) const { + if (doLayout) executePostedLayout(); + QRect a = (q_func()->isRightToLeft() ? flipX(area.normalized()) : area.normalized()); + if (viewMode == QListView::ListMode) staticListView->intersectingStaticSet(a); + else dynamicListView->intersectingDynamicSet(a); + } + + // ### FIXME: + inline void resetBatchStartRow() + { if (viewMode == QListView::ListMode) staticListView->batchStartRow = 0; + else dynamicListView->batchStartRow = 0; } + inline int batchStartRow() const + { return (viewMode == QListView::ListMode + ? staticListView->batchStartRow : dynamicListView->batchStartRow); } + inline QSize contentsSize() const + { return (viewMode == QListView::ListMode + ? staticListView->contentsSize : dynamicListView->contentsSize); } + inline void setContentsSize(int w, int h) + { if (viewMode == QListView::ListMode) staticListView->contentsSize = QSize(w, h); + else dynamicListView->contentsSize = QSize(w, h); } + + inline int flipX(int x) const + { return qMax(viewport->width(), contentsSize().width()) - x; } + inline QPoint flipX(const QPoint &p) const + { return QPoint(flipX(p.x()), p.y()); } + inline QRect flipX(const QRect &r) const + { return QRect(flipX(r.x()) - r.width(), r.y(), r.width(), r.height()); } + inline QRect viewItemRect(const QListViewItem &item) const + { if (q_func()->isRightToLeft()) return flipX(item.rect()); return item.rect(); } + + int itemIndex(const QListViewItem &item) const; + QListViewItem indexToListViewItem(const QModelIndex &index) const; + inline QModelIndex listViewItemToIndex(const QListViewItem &item) const + { return model->index(itemIndex(item), column, root); } + + QRect mapToViewport(const QRect &rect, bool greedy = false) const; + + QModelIndex closestIndex(const QRect &target, const QVector<QModelIndex> &candidates) const; + QSize itemSize(const QStyleOptionViewItem &option, const QModelIndex &index) const; + + bool selectionAllowed(const QModelIndex &index) const + { if (viewMode == QListView::ListMode && !showElasticBand) return index.isValid(); return true; } + + int horizontalScrollToValue(const QModelIndex &index, const QRect &rect, QListView::ScrollHint hint) const; + int verticalScrollToValue(const QModelIndex &index, const QRect &rect, QListView::ScrollHint hint) const; + + QItemSelection selection(const QRect &rect) const; + void selectAll(QItemSelectionModel::SelectionFlags command); + + inline void setGridSize(const QSize &size) { grid = size; } + inline QSize gridSize() const { return grid; } + inline void setWrapping(bool b) { wrap = b; } + inline bool isWrapping() const { return wrap; } + inline void setSpacing(int s) { space = s; } + inline int spacing() const { return space; } + inline void setSelectionRectVisible(bool visible) { showElasticBand = visible; } + inline bool isSelectionRectVisible() const { return showElasticBand; } + + inline QModelIndex modelIndex(int row) const { return model->index(row, column, root); } + inline bool isHidden(int row) const { return hiddenRows.contains(model->index(row, 0, root)); } + inline bool isHiddenOrDisabled(int row) const { return isHidden(row) || !isIndexEnabled(modelIndex(row)); } + + inline void removeCurrentAndDisabled(QVector<QModelIndex> *indexes, const QModelIndex ¤t) const { + QVector<QModelIndex>::iterator it = indexes->begin(); + while (it != indexes->end()) { + if (!isIndexEnabled(*it) || (*it) == current) + indexes->erase(it); + else + ++it; + } + } + + void scrollElasticBandBy(int dx, int dy); + + // ### FIXME: we only need one at a time + QDynamicListViewBase *dynamicListView; + QStaticListViewBase *staticListView; + + // ### FIXME: see if we can move the members into the dynamic/static classes + + bool wrap; + int space; + QSize grid; + + QListView::Flow flow; + QListView::Movement movement; + QListView::ResizeMode resizeMode; + QListView::LayoutMode layoutMode; + QListView::ViewMode viewMode; + + // the properties controlling the + // icon- or list-view modes + enum ModeProperties { + Wrap = 1, + Spacing = 2, + GridSize = 4, + Flow = 8, + Movement = 16, + ResizeMode = 32, + SelectionRectVisible = 64 + }; + + uint modeProperties : 8; + + QRect layoutBounds; + + // used for intersecting set + mutable QVector<QModelIndex> intersectVector; + + // timers + QBasicTimer batchLayoutTimer; + + // used for hidden items + QVector<QPersistentModelIndex> hiddenRows; + + int column; + bool uniformItemSizes; + mutable QSize cachedItemSize; + int batchSize; + + QRect elasticBand; + bool showElasticBand; +}; + +// inline implementations + +inline int QCommonListViewBase::spacing() const { return dd->spacing(); } +inline bool QCommonListViewBase::isWrapping() const { return dd->isWrapping(); } +inline QSize QCommonListViewBase::gridSize() const { return dd->gridSize(); } +inline QListView::Flow QCommonListViewBase::flow() const { return dd->flow; } +inline QListView::Movement QCommonListViewBase::movement() const { return dd->movement; } + +inline QPoint QCommonListViewBase::offset() const { return dd->offset(); } +inline QPoint QCommonListViewBase::pressedPosition() const { return dd->pressedPosition; } +inline bool QCommonListViewBase::uniformItemSizes() const { return dd->uniformItemSizes; } +inline int QCommonListViewBase::column() const { return dd->column; } + +inline int QCommonListViewBase::verticalScrollBarValue() const { return qq->verticalScrollBar()->value(); } +inline int QCommonListViewBase::horizontalScrollBarValue() const { return qq->horizontalScrollBar()->value(); } +inline QListView::ScrollMode QCommonListViewBase::verticalScrollMode() const { return qq->verticalScrollMode(); } +inline QListView::ScrollMode QCommonListViewBase::horizontalScrollMode() const { return qq->horizontalScrollMode(); } + +inline QModelIndex QCommonListViewBase::modelIndex(int row) const + { return dd->model->index(row, dd->column, dd->root); } +inline int QCommonListViewBase::rowCount() const { return dd->model->rowCount(dd->root); } + +inline QStyleOptionViewItemV4 QCommonListViewBase::viewOptions() const { return dd->viewOptionsV4(); } +inline QWidget *QCommonListViewBase::viewport() const { return dd->viewport; } +inline QRect QCommonListViewBase::clipRect() const { return dd->clipRect(); } + +inline QSize QCommonListViewBase::cachedItemSize() const { return dd->cachedItemSize; } +inline QRect QCommonListViewBase::viewItemRect(const QListViewItem &item) const { return dd->viewItemRect(item); } +inline QSize QCommonListViewBase::itemSize(const QStyleOptionViewItemV2 &opt, const QModelIndex &idx) const + { return dd->itemSize(opt, idx); } + +inline QAbstractItemDelegate *QCommonListViewBase::delegate(const QModelIndex &idx) const + { return dd->delegateForIndex(idx); } + +inline bool QCommonListViewBase::isHidden(int row) const { return dd->isHidden(row); } +inline int QCommonListViewBase::hiddenCount() const { return dd->hiddenRows.count(); } + +inline void QCommonListViewBase::clearIntersections() const { dd->intersectVector.clear(); } +inline void QCommonListViewBase::appendToIntersections(const QModelIndex &idx) const { dd->intersectVector.append(idx); } + +inline bool QCommonListViewBase::isRightToLeft() const { return qq->isRightToLeft(); } + +QT_END_NAMESPACE + +#endif // QT_NO_LISTVIEW + +#endif // QLISTVIEW_P_H diff --git a/src/gui/itemviews/qlistwidget.cpp b/src/gui/itemviews/qlistwidget.cpp new file mode 100644 index 0000000..7a366d1 --- /dev/null +++ b/src/gui/itemviews/qlistwidget.cpp @@ -0,0 +1,1865 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlistwidget.h" + +#ifndef QT_NO_LISTWIDGET +#include <qitemdelegate.h> +#include <private/qlistview_p.h> +#include <private/qwidgetitemdata_p.h> +#include <private/qlistwidget_p.h> + +QT_BEGIN_NAMESPACE + +// workaround for VC++ 6.0 linker bug (?) +typedef bool(*LessThan)(const QPair<QListWidgetItem*,int>&,const QPair<QListWidgetItem*,int>&); + +class QListWidgetMimeData : public QMimeData +{ + Q_OBJECT +public: + QList<QListWidgetItem*> items; +}; + +QT_BEGIN_INCLUDE_NAMESPACE +#include "qlistwidget.moc" +QT_END_INCLUDE_NAMESPACE + +QListModel::QListModel(QListWidget *parent) + : QAbstractListModel(parent) +{ +} + +QListModel::~QListModel() +{ + clear(); +} + +void QListModel::clear() +{ + for (int i = 0; i < items.count(); ++i) { + if (items.at(i)) { + items.at(i)->d->theid = -1; + items.at(i)->view = 0; + delete items.at(i); + } + } + items.clear(); + reset(); +} + +QListWidgetItem *QListModel::at(int row) const +{ + return items.value(row); +} + +void QListModel::remove(QListWidgetItem *item) +{ + if (!item) + return; + int row = items.indexOf(item); // ### use index(item) - it's faster + Q_ASSERT(row != -1); + beginRemoveRows(QModelIndex(), row, row); + items.at(row)->d->theid = -1; + items.at(row)->view = 0; + items.removeAt(row); + endRemoveRows(); +} + +void QListModel::insert(int row, QListWidgetItem *item) +{ + if (!item) + return; + + item->view = qobject_cast<QListWidget*>(QObject::parent()); + if (item->view && item->view->isSortingEnabled()) { + // sorted insertion + QList<QListWidgetItem*>::iterator it; + it = sortedInsertionIterator(items.begin(), items.end(), + item->view->sortOrder(), item); + row = qMax(it - items.begin(), 0); + } else { + if (row < 0) + row = 0; + else if (row > items.count()) + row = items.count(); + } + beginInsertRows(QModelIndex(), row, row); + items.insert(row, item); + item->d->theid = row; + endInsertRows(); +} + +void QListModel::insert(int row, const QStringList &labels) +{ + const int count = labels.count(); + if (count <= 0) + return; + QListWidget *view = qobject_cast<QListWidget*>(QObject::parent()); + if (view && view->isSortingEnabled()) { + // sorted insertion + for (int i = 0; i < count; ++i) { + QListWidgetItem *item = new QListWidgetItem(labels.at(i)); + insert(row, item); + } + } else { + if (row < 0) + row = 0; + else if (row > items.count()) + row = items.count(); + beginInsertRows(QModelIndex(), row, row + count - 1); + for (int i = 0; i < count; ++i) { + QListWidgetItem *item = new QListWidgetItem(labels.at(i)); + item->d->theid = row; + item->view = qobject_cast<QListWidget*>(QObject::parent()); + items.insert(row++, item); + } + endInsertRows(); + } +} + +QListWidgetItem *QListModel::take(int row) +{ + if (row < 0 || row >= items.count()) + return 0; + + beginRemoveRows(QModelIndex(), row, row); + items.at(row)->d->theid = -1; + items.at(row)->view = 0; + QListWidgetItem *item = items.takeAt(row); + endRemoveRows(); + return item; +} + +int QListModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : items.count(); +} + +QModelIndex QListModel::index(QListWidgetItem *item) const +{ + if (!item || !item->view || static_cast<const QListModel *>(item->view->model()) != this + || items.isEmpty()) + return QModelIndex(); + int row; + const int theid = item->d->theid; + if (theid >= 0 && theid < items.count() && items.at(theid) == item) { + row = theid; + } else { // we need to search for the item + row = items.lastIndexOf(item); // lastIndexOf is an optimization in favor of indexOf + if (row == -1) // not found + return QModelIndex(); + item->d->theid = row; + } + return createIndex(row, 0, item); +} + +QModelIndex QListModel::index(int row, int column, const QModelIndex &parent) const +{ + if (hasIndex(row, column, parent)) + return createIndex(row, column, items.at(row)); + return QModelIndex(); +} + +QVariant QListModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= items.count()) + return QVariant(); + return items.at(index.row())->data(role); +} + +bool QListModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() >= items.count()) + return false; + items.at(index.row())->setData(role, value); + return true; +} + +QMap<int, QVariant> QListModel::itemData(const QModelIndex &index) const +{ + QMap<int, QVariant> roles; + if (!index.isValid() || index.row() >= items.count()) + return roles; + QListWidgetItem *itm = items.at(index.row()); + for (int i = 0; i < itm->d->values.count(); ++i) { + roles.insert(itm->d->values.at(i).role, + itm->d->values.at(i).value); + } + return roles; +} + +bool QListModel::insertRows(int row, int count, const QModelIndex &parent) +{ + if (count < 1 || row < 0 || row > rowCount() || parent.isValid()) + return false; + + beginInsertRows(QModelIndex(), row, row + count - 1); + QListWidget *view = qobject_cast<QListWidget*>(QObject::parent()); + QListWidgetItem *itm = 0; + + for (int r = row; r < row + count; ++r) { + itm = new QListWidgetItem; + itm->view = view; + itm->d->theid = r; + items.insert(r, itm); + } + + endInsertRows(); + return true; +} + +bool QListModel::removeRows(int row, int count, const QModelIndex &parent) +{ + if (count < 1 || row < 0 || (row + count) > rowCount() || parent.isValid()) + return false; + + beginRemoveRows(QModelIndex(), row, row + count - 1); + QListWidgetItem *itm = 0; + for (int r = row; r < row + count; ++r) { + itm = items.takeAt(row); + itm->view = 0; + itm->d->theid = -1; + delete itm; + } + endRemoveRows(); + return true; +} + +Qt::ItemFlags QListModel::flags(const QModelIndex &index) const +{ + if (!index.isValid() || index.row() >= items.count() || index.model() != this) + return Qt::ItemIsDropEnabled; // we allow drops outside the items + return items.at(index.row())->flags(); +} + +void QListModel::sort(int column, Qt::SortOrder order) +{ + if (column != 0) + return; + + emit layoutAboutToBeChanged(); + + QVector < QPair<QListWidgetItem*,int> > sorting(items.count()); + for (int i = 0; i < items.count(); ++i) { + QListWidgetItem *item = items.at(i); + sorting[i].first = item; + sorting[i].second = i; + } + + LessThan compare = (order == Qt::AscendingOrder ? &itemLessThan : &itemGreaterThan); + qSort(sorting.begin(), sorting.end(), compare); + QModelIndexList fromIndexes; + QModelIndexList toIndexes; + for (int r = 0; r < sorting.count(); ++r) { + QListWidgetItem *item = sorting.at(r).first; + toIndexes.append(createIndex(r, 0, item)); + fromIndexes.append(createIndex(sorting.at(r).second, 0, sorting.at(r).first)); + items[r] = sorting.at(r).first; + } + changePersistentIndexList(fromIndexes, toIndexes); + + emit layoutChanged(); +} + +/** + * This function assumes that all items in the model except the items that are between + * (inclusive) start and end are sorted. + * With these assumptions, this function can ensure that the model is sorted in a + * much more efficient way than doing a naive 'sort everything'. + * (provided that the range is relatively small compared to the total number of items) + */ +void QListModel::ensureSorted(int column, Qt::SortOrder order, int start, int end) +{ + if (column != 0) + return; + + int count = end - start + 1; + QVector < QPair<QListWidgetItem*,int> > sorting(count); + for (int i = 0; i < count; ++i) { + sorting[i].first = items.at(start + i); + sorting[i].second = start + i; + } + + LessThan compare = (order == Qt::AscendingOrder ? &itemLessThan : &itemGreaterThan); + qSort(sorting.begin(), sorting.end(), compare); + + QModelIndexList oldPersistentIndexes = persistentIndexList(); + QModelIndexList newPersistentIndexes = oldPersistentIndexes; + QList<QListWidgetItem*> tmp = items; + QList<QListWidgetItem*>::iterator lit = tmp.begin(); + bool changed = false; + for (int i = 0; i < count; ++i) { + int oldRow = sorting.at(i).second; + QListWidgetItem *item = tmp.takeAt(oldRow); + lit = sortedInsertionIterator(lit, tmp.end(), order, item); + int newRow = qMax(lit - tmp.begin(), 0); + lit = tmp.insert(lit, item); + if (newRow != oldRow) { + changed = true; + for (int j = i + 1; j < count; ++j) { + int otherRow = sorting.at(j).second; + if (oldRow < otherRow && newRow >= otherRow) + --sorting[j].second; + else if (oldRow > otherRow && newRow <= otherRow) + ++sorting[j].second; + } + for (int k = 0; k < newPersistentIndexes.count(); ++k) { + QModelIndex pi = newPersistentIndexes.at(k); + int oldPersistentRow = pi.row(); + int newPersistentRow = oldPersistentRow; + if (oldPersistentRow == oldRow) + newPersistentRow = newRow; + else if (oldRow < oldPersistentRow && newRow >= oldPersistentRow) + newPersistentRow = oldPersistentRow - 1; + else if (oldRow > oldPersistentRow && newRow <= oldPersistentRow) + newPersistentRow = oldPersistentRow + 1; + if (newPersistentRow != oldPersistentRow) + newPersistentIndexes[k] = createIndex(newPersistentRow, + pi.column(), pi.internalPointer()); + } + } + } + + if (changed) { + emit layoutAboutToBeChanged(); + items = tmp; + changePersistentIndexList(oldPersistentIndexes, newPersistentIndexes); + emit layoutChanged(); + } +} + +bool QListModel::itemLessThan(const QPair<QListWidgetItem*,int> &left, + const QPair<QListWidgetItem*,int> &right) +{ + return (*left.first) < (*right.first); +} + +bool QListModel::itemGreaterThan(const QPair<QListWidgetItem*,int> &left, + const QPair<QListWidgetItem*,int> &right) +{ + return (*right.first) < (*left.first); +} + +QList<QListWidgetItem*>::iterator QListModel::sortedInsertionIterator( + const QList<QListWidgetItem*>::iterator &begin, + const QList<QListWidgetItem*>::iterator &end, + Qt::SortOrder order, QListWidgetItem *item) +{ + if (order == Qt::AscendingOrder) + return qLowerBound(begin, end, item, QListModelLessThan()); + return qLowerBound(begin, end, item, QListModelGreaterThan()); +} + +void QListModel::itemChanged(QListWidgetItem *item) +{ + QModelIndex idx = index(item); + emit dataChanged(idx, idx); +} + +QStringList QListModel::mimeTypes() const +{ + const QListWidget *view = qobject_cast<const QListWidget*>(QObject::parent()); + return view->mimeTypes(); +} + +QMimeData *QListModel::internalMimeData() const +{ + return QAbstractItemModel::mimeData(cachedIndexes); +} + +QMimeData *QListModel::mimeData(const QModelIndexList &indexes) const +{ + QList<QListWidgetItem*> itemlist; + for (int i = 0; i < indexes.count(); ++i) + itemlist << at(indexes.at(i).row()); + const QListWidget *view = qobject_cast<const QListWidget*>(QObject::parent()); + + cachedIndexes = indexes; + QMimeData *mimeData = view->mimeData(itemlist); + cachedIndexes.clear(); + return mimeData; +} + +#ifndef QT_NO_DRAGANDDROP +bool QListModel::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &index) +{ + Q_UNUSED(column); + QListWidget *view = qobject_cast<QListWidget*>(QObject::parent()); + if (index.isValid()) + row = index.row(); + else if (row == -1) + row = items.count(); + + return view->dropMimeData(row, data, action); +} + +Qt::DropActions QListModel::supportedDropActions() const +{ + const QListWidget *view = qobject_cast<const QListWidget*>(QObject::parent()); + return view->supportedDropActions(); +} +#endif // QT_NO_DRAGANDDROP + +/*! + \class QListWidgetItem + \brief The QListWidgetItem class provides an item for use with the + QListWidget item view class. + + \ingroup model-view + + QListWidgetItem is used to represent items in a list provided by the + QListWidget class. Each item can hold several pieces of information, + and will display these appropriately. + + The item view convenience classes use a classic item-based interface + rather than a pure model/view approach. For a more flexible list view + widget, consider using the QListView class with a standard model. + + List items can be automatically inserted into a list when they are + constructed by specifying the list widget: + + \snippet doc/src/snippets/qlistwidget-using/mainwindow.cpp 2 + + They can also be created without a parent widget, and later inserted into + a list (see \l{QListWidget::insertItem()}). + + List items are typically used to display text() and an icon(). These are + set with the setText() and setIcon() functions. The appearance of the text + can be customized with setFont(), setForeground(), and setBackground(). + Text in list items can be aligned using the setTextAlignment() function. + Tooltips, status tips and "What's This?" help can be added to list items + with setToolTip(), setStatusTip(), and setWhatsThis(). + + By default, items are enabled, selectable, checkable, and can be the source + of a drag and drop operation. + Each item's flags can be changed by calling setFlags() with the appropriate + value (see \l{Qt::ItemFlags}). Checkable items can be checked, unchecked and + partially checked with the setCheckState() function. The corresponding + checkState() function indicates what check state the item currently has. + + The isHidden() function can be used to determine whether the + item is hidden. Items can be hidden with setHidden(). + + \section1 Subclassing + + When subclassing QListWidgetItem to provide custom items, it is possible to + define new types for them so that they can be distinguished from standard + items. The constructors for subclasses that require this feature need to + call the base class constructor with a new type value equal to or greater + than \l UserType. + + \sa QListWidget, {Model/View Programming}, QTreeWidgetItem, QTableWidgetItem +*/ + +/*! + \enum QListWidgetItem::ItemType + + This enum describes the types that are used to describe list widget items. + + \value Type The default type for list widget items. + \value UserType The minimum value for custom types. Values below UserType are + reserved by Qt. + + You can define new user types in QListWidgetItem subclasses to ensure that + custom items are treated specially. + + \sa type() +*/ + +/*! + \fn int QListWidgetItem::type() const + + Returns the type passed to the QListWidgetItem constructor. +*/ + +/*! + \fn QListWidget *QListWidgetItem::listWidget() const + + Returns the list widget that contains the item. +*/ + +/*! + \fn void QListWidgetItem::setSelected(bool select) + \since 4.2 + + Sets the selected state of the item to \a select. + + \sa isSelected() +*/ + +/*! + \fn bool QListWidgetItem::isSelected() const + \since 4.2 + + Returns true if the item is selected, otherwise returns false. + + \sa setSelected() +*/ + +/*! + \fn void QListWidgetItem::setHidden(bool hide) + \since 4.2 + + Hides the item if \a hide is true, otherwise shows the item. + + \sa isHidden() +*/ + +/*! + \fn bool QListWidgetItem::isHidden() const + \since 4.2 + + Returns true if the item is hidden, otherwise returns false. + + \sa setHidden() +*/ + +/*! + \fn QListWidgetItem::QListWidgetItem(QListWidget *parent, int type) + + Constructs an empty list widget item of the specified \a type with the + given \a parent. + If the parent is not specified, the item will need to be inserted into a + list widget with QListWidget::insertItem(). + + \sa type() +*/ +QListWidgetItem::QListWidgetItem(QListWidget *view, int type) + : rtti(type), view(view), d(new QListWidgetItemPrivate(this)), + itemFlags(Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled) +{ + if (QListModel *model = (view ? qobject_cast<QListModel*>(view->model()) : 0)) + model->insert(model->rowCount(), this); +} + +/*! + \fn QListWidgetItem::QListWidgetItem(const QString &text, QListWidget *parent, int type) + + Constructs an empty list widget item of the specified \a type with the + given \a text and \a parent. + If the parent is not specified, the item will need to be inserted into a + list widget with QListWidget::insertItem(). + + \sa type() +*/ +QListWidgetItem::QListWidgetItem(const QString &text, QListWidget *view, int type) + : rtti(type), view(0), d(new QListWidgetItemPrivate(this)), + itemFlags(Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled) +{ + setData(Qt::DisplayRole, text); + this->view = view; + if (QListModel *model = (view ? qobject_cast<QListModel*>(view->model()) : 0)) + model->insert(model->rowCount(), this); +} + +/*! + \fn QListWidgetItem::QListWidgetItem(const QIcon &icon, const QString &text, QListWidget *parent, int type) + + Constructs an empty list widget item of the specified \a type with the + given \a icon, \a text and \a parent. + If the parent is not specified, the item will need to be inserted into a + list widget with QListWidget::insertItem(). + + \sa type() +*/ +QListWidgetItem::QListWidgetItem(const QIcon &icon,const QString &text, + QListWidget *view, int type) + : rtti(type), view(0), d(new QListWidgetItemPrivate(this)), + itemFlags(Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled) +{ + setData(Qt::DisplayRole, text); + setData(Qt::DecorationRole, icon); + this->view = view; + if (QListModel *model = (view ? qobject_cast<QListModel*>(view->model()) : 0)) + model->insert(model->rowCount(), this); +} + +/*! + Destroys the list item. +*/ +QListWidgetItem::~QListWidgetItem() +{ + if (QListModel *model = (view ? qobject_cast<QListModel*>(view->model()) : 0)) + model->remove(this); + delete d; +} + +/*! + Creates an exact copy of the item. +*/ +QListWidgetItem *QListWidgetItem::clone() const +{ + return new QListWidgetItem(*this); +} + +/*! + This function sets the data for a given \a role to the given \a value (see + \l{Qt::ItemDataRole}). Reimplement this function if you need + extra roles or special behavior for certain roles. + + \sa Qt::ItemDataRole, data() +*/ +void QListWidgetItem::setData(int role, const QVariant &value) +{ + bool found = false; + role = (role == Qt::EditRole ? Qt::DisplayRole : role); + for (int i = 0; i < d->values.count(); ++i) { + if (d->values.at(i).role == role) { + if (d->values.at(i).value == value) + return; + d->values[i].value = value; + found = true; + break; + } + } + if (!found) + d->values.append(QWidgetItemData(role, value)); + if (QListModel *model = (view ? qobject_cast<QListModel*>(view->model()) : 0)) + model->itemChanged(this); +} + +/*! + This function returns the item's data for a given \a role (see + Qt::ItemDataRole). Reimplement this function if you need + extra roles or special behavior for certain roles. +*/ +QVariant QListWidgetItem::data(int role) const +{ + role = (role == Qt::EditRole ? Qt::DisplayRole : role); + for (int i = 0; i < d->values.count(); ++i) + if (d->values.at(i).role == role) + return d->values.at(i).value; + return QVariant(); +} + +/*! + Returns true if this item's text is less then \a other item's text; + otherwise returns false. +*/ +bool QListWidgetItem::operator<(const QListWidgetItem &other) const +{ + return text() < other.text(); +} + +#ifndef QT_NO_DATASTREAM + +/*! + Reads the item from stream \a in. + + \sa write() +*/ +void QListWidgetItem::read(QDataStream &in) +{ + in >> d->values; +} + +/*! + Writes the item to stream \a out. + + \sa read() +*/ +void QListWidgetItem::write(QDataStream &out) const +{ + out << d->values; +} + +/*! + \since 4.1 + + Constructs a copy of \a other. Note that type() and listWidget() + are not copied. + + This function is useful when reimplementing clone(). + + \sa data(), flags() +*/ +QListWidgetItem::QListWidgetItem(const QListWidgetItem &other) + : rtti(Type), view(0), + d(new QListWidgetItemPrivate(this)), + itemFlags(other.itemFlags) +{ + d->values = other.d->values; +} + +/*! + Assigns \a other's data and flags to this item. Note that type() + and listWidget() are not copied. + + This function is useful when reimplementing clone(). + + \sa data(), flags() +*/ +QListWidgetItem &QListWidgetItem::operator=(const QListWidgetItem &other) +{ + d->values = other.d->values; + itemFlags = other.itemFlags; + return *this; +} + +/*! + \relates QListWidgetItem + + Writes the list widget item \a item to stream \a out. + + This operator uses QListWidgetItem::write(). + + \sa {Format of the QDataStream Operators} +*/ +QDataStream &operator<<(QDataStream &out, const QListWidgetItem &item) +{ + item.write(out); + return out; +} + +/*! + \relates QListWidgetItem + + Reads a list widget item from stream \a in into \a item. + + This operator uses QListWidgetItem::read(). + + \sa {Format of the QDataStream Operators} +*/ +QDataStream &operator>>(QDataStream &in, QListWidgetItem &item) +{ + item.read(in); + return in; +} + +#endif // QT_NO_DATASTREAM + +/*! + \fn Qt::ItemFlags QListWidgetItem::flags() const + + Returns the item flags for this item (see \l{Qt::ItemFlags}). +*/ + +/*! + \fn QString QListWidgetItem::text() const + + Returns the list item's text. + + \sa setText() +*/ + +/*! + \fn QIcon QListWidgetItem::icon() const + + Returns the list item's icon. + + \sa setIcon(), {QAbstractItemView::iconSize}{iconSize} +*/ + +/*! + \fn QString QListWidgetItem::statusTip() const + + Returns the list item's status tip. + + \sa setStatusTip() +*/ + +/*! + \fn QString QListWidgetItem::toolTip() const + + Returns the list item's tooltip. + + \sa setToolTip() statusTip() whatsThis() +*/ + +/*! + \fn QString QListWidgetItem::whatsThis() const + + Returns the list item's "What's This?" help text. + + \sa setWhatsThis() statusTip() toolTip() +*/ + +/*! + \fn QFont QListWidgetItem::font() const + + Returns the font used to display this list item's text. +*/ + +/*! + \fn int QListWidgetItem::textAlignment() const + + Returns the text alignment for the list item (see \l{Qt::AlignmentFlag}). +*/ + +/*! + \fn QColor QListWidgetItem::backgroundColor() const + \obsolete + + This function is deprecated. Use background() instead. +*/ + +/*! + \fn QBrush QListWidgetItem::background() const + \since 4.2 + + Returns the brush used to display the list item's background. + + \sa setBackground() foreground() +*/ + +/*! + \fn QColor QListWidgetItem::textColor() const + \obsolete + + Returns the color used to display the list item's text. + + This function is deprecated. Use foreground() instead. +*/ + +/*! + \fn QBrush QListWidgetItem::foreground() const + \since 4.2 + + Returns the brush used to display the list item's foreground (e.g. text). + + \sa setForeground() background() +*/ + +/*! + \fn Qt::CheckState QListWidgetItem::checkState() const + + Returns the checked state of the list item (see \l{Qt::CheckState}). + + \sa flags() +*/ + +/*! + \fn QSize QListWidgetItem::sizeHint() const + \since 4.1 + + Returns the size hint set for the list item. +*/ + +/*! + \fn void QListWidgetItem::setSizeHint(const QSize &size) + \since 4.1 + + Sets the size hint for the list item to be \a size. + If no size hint is set, the item delegate will compute the + size hint based on the item data. +*/ + +/*! + \fn void QListWidgetItem::setFlags(Qt::ItemFlags flags) + + Sets the item flags for the list item to \a flags (see + \l{Qt::ItemFlags}). +*/ +void QListWidgetItem::setFlags(Qt::ItemFlags aflags) { + itemFlags = aflags; + if (QListModel *model = (view ? qobject_cast<QListModel*>(view->model()) : 0)) + model->itemChanged(this); +} + + +/*! + \fn void QListWidgetItem::setText(const QString &text) + + Sets the text for the list widget item's to the given \a text. + + \sa text() +*/ + +/*! + \fn void QListWidgetItem::setIcon(const QIcon &icon) + + Sets the icon for the list item to the given \a icon. + + \sa icon(), text(), {QAbstractItemView::iconSize}{iconSize} +*/ + +/*! + \fn void QListWidgetItem::setStatusTip(const QString &statusTip) + + Sets the status tip for the list item to the text specified by + \a statusTip. QListWidget mouse tracking needs to be enabled for this + feature to work. + + \sa statusTip() setToolTip() setWhatsThis() +*/ + +/*! + \fn void QListWidgetItem::setToolTip(const QString &toolTip) + + Sets the tooltip for the list item to the text specified by \a toolTip. + + \sa toolTip() setStatusTip() setWhatsThis() +*/ + +/*! + \fn void QListWidgetItem::setWhatsThis(const QString &whatsThis) + + Sets the "What's This?" help for the list item to the text specified + by \a whatsThis. + + \sa whatsThis() setStatusTip() setToolTip() +*/ + +/*! + \fn void QListWidgetItem::setFont(const QFont &font) + + Sets the font used when painting the item to the given \a font. +*/ + +/*! + \fn void QListWidgetItem::setTextAlignment(int alignment) + + Sets the list item's text alignment to \a alignment (see + \l{Qt::AlignmentFlag}). +*/ + +/*! + \fn void QListWidgetItem::setBackgroundColor(const QColor &color) + \obsolete + + This function is deprecated. Use setBackground() instead. +*/ + +/*! + \fn void QListWidgetItem::setBackground(const QBrush &brush) + \since 4.2 + + Sets the background brush of the list item to the given \a brush. + + \sa background() setForeground() +*/ + +/*! + \fn void QListWidgetItem::setTextColor(const QColor &color) + \obsolete + + This function is deprecated. Use setForeground() instead. +*/ + +/*! + \fn void QListWidgetItem::setForeground(const QBrush &brush) + \since 4.2 + + Sets the foreground brush of the list item to the given \a brush. + + \sa foreground() setBackground() +*/ + +/*! + \fn void QListWidgetItem::setCheckState(Qt::CheckState state) + + Sets the check state of the list item to \a state. + + \sa checkState() +*/ + +void QListWidgetPrivate::setup() +{ + Q_Q(QListWidget); + q->QListView::setModel(new QListModel(q)); + // view signals + QObject::connect(q, SIGNAL(pressed(QModelIndex)), q, SLOT(_q_emitItemPressed(QModelIndex))); + QObject::connect(q, SIGNAL(clicked(QModelIndex)), q, SLOT(_q_emitItemClicked(QModelIndex))); + QObject::connect(q, SIGNAL(doubleClicked(QModelIndex)), + q, SLOT(_q_emitItemDoubleClicked(QModelIndex))); + QObject::connect(q, SIGNAL(activated(QModelIndex)), + q, SLOT(_q_emitItemActivated(QModelIndex))); + QObject::connect(q, SIGNAL(entered(QModelIndex)), q, SLOT(_q_emitItemEntered(QModelIndex))); + QObject::connect(model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), + q, SLOT(_q_emitItemChanged(QModelIndex))); + QObject::connect(q->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + q, SLOT(_q_emitCurrentItemChanged(QModelIndex,QModelIndex))); + QObject::connect(q->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + q, SIGNAL(itemSelectionChanged())); + QObject::connect(model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), + q, SLOT(_q_dataChanged(QModelIndex,QModelIndex))); + QObject::connect(model(), SIGNAL(columnsRemoved(QModelIndex,int,int)), q, SLOT(_q_sort())); +} + +void QListWidgetPrivate::_q_emitItemPressed(const QModelIndex &index) +{ + Q_Q(QListWidget); + emit q->itemPressed(model()->at(index.row())); +} + +void QListWidgetPrivate::_q_emitItemClicked(const QModelIndex &index) +{ + Q_Q(QListWidget); + emit q->itemClicked(model()->at(index.row())); +} + +void QListWidgetPrivate::_q_emitItemDoubleClicked(const QModelIndex &index) +{ + Q_Q(QListWidget); + emit q->itemDoubleClicked(model()->at(index.row())); +} + +void QListWidgetPrivate::_q_emitItemActivated(const QModelIndex &index) +{ + Q_Q(QListWidget); + emit q->itemActivated(model()->at(index.row())); +} + +void QListWidgetPrivate::_q_emitItemEntered(const QModelIndex &index) +{ + Q_Q(QListWidget); + emit q->itemEntered(model()->at(index.row())); +} + +void QListWidgetPrivate::_q_emitItemChanged(const QModelIndex &index) +{ + Q_Q(QListWidget); + emit q->itemChanged(model()->at(index.row())); +} + +void QListWidgetPrivate::_q_emitCurrentItemChanged(const QModelIndex ¤t, + const QModelIndex &previous) +{ + Q_Q(QListWidget); + QPersistentModelIndex persistentCurrent = current; + QListWidgetItem *currentItem = model()->at(persistentCurrent.row()); + emit q->currentItemChanged(currentItem, model()->at(previous.row())); + + //persistentCurrent is invalid if something changed the model in response + //to the currentItemChanged signal emission and the item was removed + if (!persistentCurrent.isValid()) { + currentItem = 0; + } + + emit q->currentTextChanged(currentItem ? currentItem->text() : QString()); + emit q->currentRowChanged(persistentCurrent.row()); +} + +void QListWidgetPrivate::_q_sort() +{ + if (sortingEnabled) + model()->sort(0, sortOrder); +} + +void QListWidgetPrivate::_q_dataChanged(const QModelIndex &topLeft, + const QModelIndex &bottomRight) +{ + if (sortingEnabled && topLeft.isValid() && bottomRight.isValid()) + model()->ensureSorted(topLeft.column(), sortOrder, + topLeft.row(), bottomRight.row()); +} + +/*! + \class QListWidget + \brief The QListWidget class provides an item-based list widget. + + \ingroup model-view + \mainclass + + QListWidget is a convenience class that provides a list view similar to + the one supplied by QListView, but with a classic item-based interface + for adding and removing items. QListWidget uses an internal model to + manage each QListWidgetItem in the list. + + For a more flexible list view widget, use the QListView class with a + standard model. + + List widgets are constructed in the same way as other widgets: + + \snippet doc/src/snippets/qlistwidget-using/mainwindow.cpp 0 + + The selectionMode() of a list widget determines how many of the items in + the list can be selected at the same time, and whether complex selections + of items can be created. This can be set with the setSelectionMode() + function. + + There are two ways to add items to the list: they can be constructed with + the list widget as their parent widget, or they can be constructed with + no parent widget and added to the list later. If a list widget already + exists when the items are constructed, the first method is easier to use: + + \snippet doc/src/snippets/qlistwidget-using/mainwindow.cpp 1 + + If you need to insert a new item into the list at a particular position, + it is more required to construct the item without a parent widget and + use the insertItem() function to place it within the list. The list + widget will take ownership of the item. + + \snippet doc/src/snippets/qlistwidget-using/mainwindow.cpp 6 + \snippet doc/src/snippets/qlistwidget-using/mainwindow.cpp 7 + + For multiple items, insertItems() can be used instead. The number of + items in the list is found with the count() function. + To remove items from the list, use takeItem(). + + The current item in the list can be found with currentItem(), and changed + with setCurrentItem(). The user can also change the current item by + navigating with the keyboard or clicking on a different item. When the + current item changes, the currentItemChanged() signal is emitted with the + new current item and the item that was previously current. + + \table 100% + \row \o \inlineimage windowsxp-listview.png Screenshot of a Windows XP style list widget + \o \inlineimage macintosh-listview.png Screenshot of a Macintosh style table widget + \o \inlineimage plastique-listview.png Screenshot of a Plastique style table widget + \row \o A \l{Windows XP Style Widget Gallery}{Windows XP style} list widget. + \o A \l{Macintosh Style Widget Gallery}{Macintosh style} list widget. + \o A \l{Plastique Style Widget Gallery}{Plastique style} list widget. + \endtable + + \sa QListWidgetItem, QListView, QTreeView, {Model/View Programming}, + {Config Dialog Example} +*/ + +/*! + \fn void QListWidget::addItem(QListWidgetItem *item) + + Inserts the \a item at the the end of the list widget. + + \warning A QListWidgetItem can only be added to a + QListWidget once. Adding the same QListWidgetItem multiple + times to a QListWidget will result in undefined behavior. + + \sa insertItem() +*/ + +/*! + \fn void QListWidget::addItem(const QString &label) + + Inserts an item with the text \a label at the end of the list + widget. +*/ + +/*! + \fn void QListWidget::addItems(const QStringList &labels) + + Inserts items with the text \a labels at the end of the list widget. + + \sa insertItems() +*/ + +/*! + \fn void QListWidget::itemPressed(QListWidgetItem *item) + + This signal is emitted with the specified \a item when a mouse button is pressed + on an item in the widget. + + \sa itemClicked(), itemDoubleClicked() +*/ + +/*! + \fn void QListWidget::itemClicked(QListWidgetItem *item) + + This signal is emitted with the specified \a item when a mouse button is clicked + on an item in the widget. + + \sa itemPressed(), itemDoubleClicked() +*/ + +/*! + \fn void QListWidget::itemDoubleClicked(QListWidgetItem *item) + + This signal is emitted with the specified \a item when a mouse button is double + clicked on an item in the widget. + + \sa itemClicked(), itemPressed() +*/ + +/*! + \fn void QListWidget::itemActivated(QListWidgetItem *item) + + This signal is emitted when the \a item is activated. The \a item + is activated when the user clicks or double clicks on it, + depending on the system configuration. It is also activated when + the user presses the activation key (on Windows and X11 this is + the \gui Return key, on Mac OS X it is \key{Ctrl+0}). +*/ + +/*! + \fn void QListWidget::itemEntered(QListWidgetItem *item) + + This signal is emitted when the mouse cursor enters an item. The + \a item is the item entered. This signal is only emitted when + mouseTracking is turned on, or when a mouse button is pressed + while moving into an item. +*/ + +/*! + \fn void QListWidget::itemChanged(QListWidgetItem *item) + + This signal is emitted whenever the data of \a item has changed. +*/ + +/*! + \fn void QListWidget::currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) + + This signal is emitted whenever the current item changes. The \a + previous item is the item that previously had the focus, \a + current is the new current item. +*/ + +/*! + \fn void QListWidget::currentTextChanged(const QString ¤tText) + + This signal is emitted whenever the current item changes. The \a currentText + is the text data in the current item. If there is no current item, the \a currentText + is invalid. +*/ + +/*! + \fn void QListWidget::currentRowChanged(int currentRow) + + This signal is emitted whenever the current item changes. The \a currentRow + is the row of the current item. If there is no current item, the \a currentRow is -1. +*/ + +/*! + \fn void QListWidget::itemSelectionChanged() + + This signal is emitted whenever the selection changes. + + \sa selectedItems() isItemSelected() currentItemChanged() +*/ + +/*! + \since 4.3 + + \fn void QListWidget::removeItemWidget(QListWidgetItem *item) + + Removes the widget set on the given \a item. +*/ + +/*! + Constructs an empty QListWidget with the given \a parent. +*/ + +QListWidget::QListWidget(QWidget *parent) + : QListView(*new QListWidgetPrivate(), parent) +{ + Q_D(QListWidget); + d->setup(); +} + +/*! + Destroys the list widget and all its items. +*/ + +QListWidget::~QListWidget() +{ +} + +/*! + Returns the item that occupies the given \a row in the list if one has been + set; otherwise returns 0. + + \sa row() +*/ + +QListWidgetItem *QListWidget::item(int row) const +{ + Q_D(const QListWidget); + if (row < 0 || row >= d->model()->rowCount()) + return 0; + return d->model()->at(row); +} + +/*! + Returns the row containing the given \a item. + + \sa item() +*/ + +int QListWidget::row(const QListWidgetItem *item) const +{ + Q_D(const QListWidget); + return d->model()->index(const_cast<QListWidgetItem*>(item)).row(); +} + + +/*! + Inserts the \a item at the position in the list given by \a row. + + \sa addItem() +*/ + +void QListWidget::insertItem(int row, QListWidgetItem *item) +{ + Q_D(QListWidget); + if (item && !item->view) + d->model()->insert(row, item); +} + +/*! + Inserts an item with the text \a label in the list widget at the + position given by \a row. + + \sa addItem() +*/ + +void QListWidget::insertItem(int row, const QString &label) +{ + Q_D(QListWidget); + d->model()->insert(row, new QListWidgetItem(label)); +} + +/*! + Inserts items from the list of \a labels into the list, starting at the + given \a row. + + \sa insertItem(), addItem() +*/ + +void QListWidget::insertItems(int row, const QStringList &labels) +{ + Q_D(QListWidget); + d->model()->insert(row, labels); +} + +/*! + Removes and returns the item from the given \a row in the list widget; otherwise + returns 0. + + Items removed from a list widget will not be managed by Qt, and will need to be + deleted manually. + + \sa insertItem(), addItem() +*/ + +QListWidgetItem *QListWidget::takeItem(int row) +{ + Q_D(QListWidget); + if (row < 0 || row >= d->model()->rowCount()) + return 0; + return d->model()->take(row); +} + +/*! + \property QListWidget::count + \brief the number of items in the list including any hidden items. +*/ + +int QListWidget::count() const +{ + Q_D(const QListWidget); + return d->model()->rowCount(); +} + +/*! + Returns the current item. +*/ +QListWidgetItem *QListWidget::currentItem() const +{ + Q_D(const QListWidget); + return d->model()->at(currentIndex().row()); +} + + +/*! + Sets the current item to \a item. + + Depending on the current selection mode, the item may also be selected. +*/ +void QListWidget::setCurrentItem(QListWidgetItem *item) +{ + setCurrentRow(row(item)); +} + +/*! + \since 4.4 + Set the current item to \a item, using the given \a command. +*/ +void QListWidget::setCurrentItem(QListWidgetItem *item, QItemSelectionModel::SelectionFlags command) +{ + setCurrentRow(row(item), command); +} + +/*! + \property QListWidget::currentRow + \brief the row of the current item. + + Depending on the current selection mode, the row may also be selected. +*/ + +int QListWidget::currentRow() const +{ + return currentIndex().row(); +} + +void QListWidget::setCurrentRow(int row) +{ + Q_D(QListWidget); + QModelIndex index = d->model()->index(row); + if (d->selectionMode == SingleSelection) + selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); + else if (d->selectionMode == NoSelection) + selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + else + selectionModel()->setCurrentIndex(index, QItemSelectionModel::SelectCurrent); +} + +/*! + \since 4.4 + + Sets the current row to be the given \a row, using the given \a command, +*/ +void QListWidget::setCurrentRow(int row, QItemSelectionModel::SelectionFlags command) +{ + Q_D(QListWidget); + d->selectionModel->setCurrentIndex(d->model()->index(row), command); +} + +/*! + Returns a pointer to the item at the coordinates \a p. +*/ +QListWidgetItem *QListWidget::itemAt(const QPoint &p) const +{ + Q_D(const QListWidget); + return d->model()->at(indexAt(p).row()); + +} + +/*! + \fn QListWidgetItem *QListWidget::itemAt(int x, int y) const + \overload + + Returns a pointer to the item at the coordinates (\a x, \a y). +*/ + + +/*! + Returns the rectangle on the viewport occupied by the item at \a item. +*/ +QRect QListWidget::visualItemRect(const QListWidgetItem *item) const +{ + Q_D(const QListWidget); + QModelIndex index = d->model()->index(const_cast<QListWidgetItem*>(item)); + return visualRect(index); +} + +/*! + Sorts all the items in the list widget according to the specified \a order. +*/ +void QListWidget::sortItems(Qt::SortOrder order) +{ + Q_D(QListWidget); + d->sortOrder = order; + d->model()->sort(0, order); +} + +/*! + \since 4.2 + \property QListWidget::sortingEnabled + \brief whether sorting is enabled + + If this property is true, sorting is enabled for the list; if the + property is false, sorting is not enabled. The default value is false. +*/ +void QListWidget::setSortingEnabled(bool enable) +{ + Q_D(QListWidget); + d->sortingEnabled = enable; +} + +bool QListWidget::isSortingEnabled() const +{ + Q_D(const QListWidget); + return d->sortingEnabled; +} + +/*! + \internal +*/ +Qt::SortOrder QListWidget::sortOrder() const +{ + Q_D(const QListWidget); + return d->sortOrder; +} + +/*! + Starts editing the \a item if it is editable. +*/ + +void QListWidget::editItem(QListWidgetItem *item) +{ + Q_D(QListWidget); + edit(d->model()->index(item)); +} + +/*! + Opens an editor for the given \a item. The editor remains open after editing. + + \sa closePersistentEditor() +*/ +void QListWidget::openPersistentEditor(QListWidgetItem *item) +{ + Q_D(QListWidget); + QModelIndex index = d->model()->index(item); + QAbstractItemView::openPersistentEditor(index); +} + +/*! + Closes the persistent editor for the given \a item. + + \sa openPersistentEditor() +*/ +void QListWidget::closePersistentEditor(QListWidgetItem *item) +{ + Q_D(QListWidget); + QModelIndex index = d->model()->index(item); + QAbstractItemView::closePersistentEditor(index); +} + +/*! + \since 4.1 + + Returns the widget displayed in the given \a item. +*/ +QWidget *QListWidget::itemWidget(QListWidgetItem *item) const +{ + Q_D(const QListWidget); + QModelIndex index = d->model()->index(item); + return QAbstractItemView::indexWidget(index); +} + +/*! + \since 4.1 + + Sets the \a widget to be displayed in the give \a item. + + This function should only be used to display static content in the place of a list + widget item. If you want to display custom dynamic content or implement a custom + editor widget, use QListView and subclass QItemDelegate instead. + + \sa {Delegate Classes} +*/ +void QListWidget::setItemWidget(QListWidgetItem *item, QWidget *widget) +{ + Q_D(QListWidget); + QModelIndex index = d->model()->index(item); + QAbstractItemView::setIndexWidget(index, widget); +} + +/*! + Returns true if \a item is selected; otherwise returns false. + + \obsolete + + This function is deprecated. Use \l{QListWidgetItem::isSelected()} instead. +*/ +bool QListWidget::isItemSelected(const QListWidgetItem *item) const +{ + Q_D(const QListWidget); + QModelIndex index = d->model()->index(const_cast<QListWidgetItem*>(item)); + return selectionModel()->isSelected(index); +} + +/*! + Selects or deselects the given \a item depending on whether \a select is + true of false. + + \obsolete + + This function is deprecated. Use \l{QListWidgetItem::setSelected()} instead. +*/ +void QListWidget::setItemSelected(const QListWidgetItem *item, bool select) +{ + Q_D(QListWidget); + QModelIndex index = d->model()->index(const_cast<QListWidgetItem*>(item)); + + if (d->selectionMode == SingleSelection) { + selectionModel()->select(index, select + ? QItemSelectionModel::ClearAndSelect + : QItemSelectionModel::Deselect); + } else if (d->selectionMode != NoSelection) { + selectionModel()->select(index, select + ? QItemSelectionModel::Select + : QItemSelectionModel::Deselect); + } + +} + +/*! + Returns a list of all selected items in the list widget. +*/ + +QList<QListWidgetItem*> QListWidget::selectedItems() const +{ + Q_D(const QListWidget); + QModelIndexList indexes = selectionModel()->selectedIndexes(); + QList<QListWidgetItem*> items; + for (int i = 0; i < indexes.count(); ++i) + items.append(d->model()->at(indexes.at(i).row())); + return items; +} + +/*! + Finds items with the text that matches the string \a text using the given \a flags. +*/ + +QList<QListWidgetItem*> QListWidget::findItems(const QString &text, Qt::MatchFlags flags) const +{ + Q_D(const QListWidget); + QModelIndexList indexes = d->model()->match(model()->index(0, 0, QModelIndex()), + Qt::DisplayRole, text, -1, flags); + QList<QListWidgetItem*> items; + for (int i = 0; i < indexes.size(); ++i) + items.append(d->model()->at(indexes.at(i).row())); + return items; +} + +/*! + Returns true if the \a item is explicitly hidden; otherwise returns false. + + \obsolete + + This function is deprecated. Use \l{QListWidgetItem::isHidden()} instead. +*/ +bool QListWidget::isItemHidden(const QListWidgetItem *item) const +{ + return isRowHidden(row(item)); +} + +/*! + If \a hide is true, the \a item will be hidden; otherwise it will be shown. + + \obsolete + + This function is deprecated. Use \l{QListWidgetItem::setHidden()} instead. +*/ +void QListWidget::setItemHidden(const QListWidgetItem *item, bool hide) +{ + setRowHidden(row(item), hide); +} + +/*! + Scrolls the view if necessary to ensure that the \a item is + visible. The \a hint parameter specifies more precisely where the + \a item should be located after the operation. +*/ + +void QListWidget::scrollToItem(const QListWidgetItem *item, QAbstractItemView::ScrollHint hint) +{ + Q_D(QListWidget); + QModelIndex index = d->model()->index(const_cast<QListWidgetItem*>(item)); + QListView::scrollTo(index, hint); +} + +/*! + Removes all items and selections in the view. + + \note All items will be permanently deleted. +*/ +void QListWidget::clear() +{ + Q_D(QListWidget); + selectionModel()->clear(); + d->model()->clear(); +} + +/*! + Returns a list of MIME types that can be used to describe a list of + listwidget items. + + \sa mimeData() +*/ +QStringList QListWidget::mimeTypes() const +{ + return d_func()->model()->QAbstractListModel::mimeTypes(); +} + +/*! + Returns an object that contains a serialized description of the specified + \a items. The format used to describe the items is obtained from the + mimeTypes() function. + + If the list of items is empty, 0 is returned rather than a serialized + empty list. +*/ +QMimeData *QListWidget::mimeData(const QList<QListWidgetItem*>) const +{ + return d_func()->model()->internalMimeData(); +} + +#ifndef QT_NO_DRAGANDDROP +/*! + Handles the \a data supplied by an external drag and drop operation + that ended with the given \a action in the given \a index. + Returns true if the data and action can be handled by the model; + otherwise returns false. + + \sa supportedDropActions() +*/ +bool QListWidget::dropMimeData(int index, const QMimeData *data, Qt::DropAction action) +{ + QModelIndex idx; + int row = index; + int column = 0; + if (dropIndicatorPosition() == QAbstractItemView::OnItem) { + // QAbstractListModel::dropMimeData will overwrite on the index if row == -1 and column == -1 + idx = model()->index(row, column); + row = -1; + column = -1; + } + return d_func()->model()->QAbstractListModel::dropMimeData(data, action , row, column, idx); +} + +/*! \reimp */ +void QListWidget::dropEvent(QDropEvent *event) { + Q_D(QListWidget); + if (event->source() == this && d->movement != Static) { + QListView::dropEvent(event); + return; + } + + if (event->source() == this && (event->dropAction() == Qt::MoveAction || + dragDropMode() == QAbstractItemView::InternalMove)) { + QModelIndex topIndex; + int col = -1; + int row = -1; + if (d->dropOn(event, &row, &col, &topIndex)) { + QList<QModelIndex> selIndexes = selectedIndexes(); + QList<QPersistentModelIndex> persIndexes; + for (int i = 0; i < selIndexes.count(); i++) + persIndexes.append(selIndexes.at(i)); + + if (persIndexes.contains(topIndex)) + return; + + QPersistentModelIndex dropRow = model()->index(row, col, topIndex); + + QList<QListWidgetItem *> taken; + for (int i = 0; i < persIndexes.count(); ++i) + taken.append(takeItem(persIndexes.at(i).row())); + + // insert them back in at their new positions + for (int i = 0; i < persIndexes.count(); ++i) { + // Either at a specific point or appended + if (row == -1) { + insertItem(count(), taken.takeFirst()); + } else { + int r = dropRow.row() >= 0 ? dropRow.row() : row; + insertItem(qMin(r, count()), taken.takeFirst()); + } + } + + event->accept(); + // Don't want QAbstractItemView to delete it because it was "moved" we already did it + event->setDropAction(Qt::CopyAction); + } + } + + QListView::dropEvent(event); +} + +/*! + Returns the drop actions supported by this view. + + \sa Qt::DropActions +*/ +Qt::DropActions QListWidget::supportedDropActions() const +{ + Q_D(const QListWidget); + return d->model()->QAbstractListModel::supportedDropActions() | Qt::MoveAction; +} +#endif // QT_NO_DRAGANDDROP + +/*! + Returns a list of pointers to the items contained in the \a data object. + If the object was not created by a QListWidget in the same process, the list + is empty. +*/ +QList<QListWidgetItem*> QListWidget::items(const QMimeData *data) const +{ + const QListWidgetMimeData *lwd = qobject_cast<const QListWidgetMimeData*>(data); + if (lwd) + return lwd->items; + return QList<QListWidgetItem*>(); +} + +/*! + Returns the QModelIndex assocated with the given \a item. +*/ + +QModelIndex QListWidget::indexFromItem(QListWidgetItem *item) const +{ + Q_D(const QListWidget); + return d->model()->index(item); +} + +/*! + Returns a pointer to the QListWidgetItem assocated with the given \a index. +*/ + +QListWidgetItem *QListWidget::itemFromIndex(const QModelIndex &index) const +{ + Q_D(const QListWidget); + if (d->isIndexValid(index)) + return d->model()->at(index.row()); + return 0; +} + +/*! + \internal +*/ +void QListWidget::setModel(QAbstractItemModel * /*model*/) +{ + Q_ASSERT(!"QListWidget::setModel() - Changing the model of the QListWidget is not allowed."); +} + +/*! + \reimp +*/ +bool QListWidget::event(QEvent *e) +{ + return QListView::event(e); +} + +QT_END_NAMESPACE + +#include "moc_qlistwidget.cpp" + +#endif // QT_NO_LISTWIDGET diff --git a/src/gui/itemviews/qlistwidget.h b/src/gui/itemviews/qlistwidget.h new file mode 100644 index 0000000..c13405d --- /dev/null +++ b/src/gui/itemviews/qlistwidget.h @@ -0,0 +1,335 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLISTWIDGET_H +#define QLISTWIDGET_H + +#include <QtGui/qlistview.h> +#include <QtCore/qvariant.h> +#include <QtCore/qvector.h> +#include <QtGui/qitemselectionmodel.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_LISTWIDGET + +class QListWidget; +class QListModel; +class QWidgetItemData; +class QListWidgetItemPrivate; + +class Q_GUI_EXPORT QListWidgetItem +{ + friend class QListModel; + friend class QListWidget; +public: + enum ItemType { Type = 0, UserType = 1000 }; + explicit QListWidgetItem(QListWidget *view = 0, int type = Type); + explicit QListWidgetItem(const QString &text, QListWidget *view = 0, int type = Type); + explicit QListWidgetItem(const QIcon &icon, const QString &text, + QListWidget *view = 0, int type = Type); + QListWidgetItem(const QListWidgetItem &other); + virtual ~QListWidgetItem(); + + virtual QListWidgetItem *clone() const; + + inline QListWidget *listWidget() const { return view; } + + inline void setSelected(bool select); + inline bool isSelected() const; + + inline void setHidden(bool hide); + inline bool isHidden() const; + + inline Qt::ItemFlags flags() const { return itemFlags; } + void setFlags(Qt::ItemFlags flags); + + inline QString text() const + { return data(Qt::DisplayRole).toString(); } + inline void setText(const QString &text); + + inline QIcon icon() const + { return qvariant_cast<QIcon>(data(Qt::DecorationRole)); } + inline void setIcon(const QIcon &icon); + + inline QString statusTip() const + { return data(Qt::StatusTipRole).toString(); } + inline void setStatusTip(const QString &statusTip); + +#ifndef QT_NO_TOOLTIP + inline QString toolTip() const + { return data(Qt::ToolTipRole).toString(); } + inline void setToolTip(const QString &toolTip); +#endif + +#ifndef QT_NO_WHATSTHIS + inline QString whatsThis() const + { return data(Qt::WhatsThisRole).toString(); } + inline void setWhatsThis(const QString &whatsThis); +#endif + + inline QFont font() const + { return qvariant_cast<QFont>(data(Qt::FontRole)); } + inline void setFont(const QFont &font); + + inline int textAlignment() const + { return data(Qt::TextAlignmentRole).toInt(); } + inline void setTextAlignment(int alignment) + { setData(Qt::TextAlignmentRole, alignment); } + + inline QColor backgroundColor() const + { return qvariant_cast<QColor>(data(Qt::BackgroundColorRole)); } + virtual void setBackgroundColor(const QColor &color) + { setData(Qt::BackgroundColorRole, color); } + + inline QBrush background() const + { return qvariant_cast<QBrush>(data(Qt::BackgroundRole)); } + inline void setBackground(const QBrush &brush) + { setData(Qt::BackgroundRole, brush); } + + inline QColor textColor() const + { return qvariant_cast<QColor>(data(Qt::TextColorRole)); } + inline void setTextColor(const QColor &color) + { setData(Qt::TextColorRole, color); } + + inline QBrush foreground() const + { return qvariant_cast<QBrush>(data(Qt::ForegroundRole)); } + inline void setForeground(const QBrush &brush) + { setData(Qt::ForegroundRole, brush); } + + inline Qt::CheckState checkState() const + { return static_cast<Qt::CheckState>(data(Qt::CheckStateRole).toInt()); } + inline void setCheckState(Qt::CheckState state) + { setData(Qt::CheckStateRole, static_cast<int>(state)); } + + inline QSize sizeHint() const + { return qvariant_cast<QSize>(data(Qt::SizeHintRole)); } + inline void setSizeHint(const QSize &size) + { setData(Qt::SizeHintRole, size); } + + virtual QVariant data(int role) const; + virtual void setData(int role, const QVariant &value); + + virtual bool operator<(const QListWidgetItem &other) const; + +#ifndef QT_NO_DATASTREAM + virtual void read(QDataStream &in); + virtual void write(QDataStream &out) const; +#endif + QListWidgetItem &operator=(const QListWidgetItem &other); + + inline int type() const { return rtti; } + +private: + int rtti; + QVector<void *> dummy; + QListWidget *view; + QListWidgetItemPrivate *d; + Qt::ItemFlags itemFlags; +}; + +inline void QListWidgetItem::setText(const QString &atext) +{ setData(Qt::DisplayRole, atext); } + +inline void QListWidgetItem::setIcon(const QIcon &aicon) +{ setData(Qt::DecorationRole, aicon); } + +inline void QListWidgetItem::setStatusTip(const QString &astatusTip) +{ setData(Qt::StatusTipRole, astatusTip); } + +#ifndef QT_NO_TOOLTIP +inline void QListWidgetItem::setToolTip(const QString &atoolTip) +{ setData(Qt::ToolTipRole, atoolTip); } +#endif + +#ifndef QT_NO_WHATSTHIS +inline void QListWidgetItem::setWhatsThis(const QString &awhatsThis) +{ setData(Qt::WhatsThisRole, awhatsThis); } +#endif + +inline void QListWidgetItem::setFont(const QFont &afont) +{ setData(Qt::FontRole, afont); } + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &out, const QListWidgetItem &item); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &in, QListWidgetItem &item); +#endif + +class QListWidgetPrivate; + +class Q_GUI_EXPORT QListWidget : public QListView +{ + Q_OBJECT + Q_PROPERTY(int count READ count) + Q_PROPERTY(int currentRow READ currentRow WRITE setCurrentRow NOTIFY currentRowChanged USER true) + Q_PROPERTY(bool sortingEnabled READ isSortingEnabled WRITE setSortingEnabled) + + friend class QListWidgetItem; + friend class QListModel; +public: + explicit QListWidget(QWidget *parent = 0); + ~QListWidget(); + + QListWidgetItem *item(int row) const; + int row(const QListWidgetItem *item) const; + void insertItem(int row, QListWidgetItem *item); + void insertItem(int row, const QString &label); + void insertItems(int row, const QStringList &labels); + inline void addItem(const QString &label) { insertItem(count(), label); } + inline void addItem(QListWidgetItem *item); + inline void addItems(const QStringList &labels) { insertItems(count(), labels); } + QListWidgetItem *takeItem(int row); + int count() const; + + QListWidgetItem *currentItem() const; + void setCurrentItem(QListWidgetItem *item); + void setCurrentItem(QListWidgetItem *item, QItemSelectionModel::SelectionFlags command); + + int currentRow() const; + void setCurrentRow(int row); + void setCurrentRow(int row, QItemSelectionModel::SelectionFlags command); + + QListWidgetItem *itemAt(const QPoint &p) const; + inline QListWidgetItem *itemAt(int x, int y) const; + QRect visualItemRect(const QListWidgetItem *item) const; + + void sortItems(Qt::SortOrder order = Qt::AscendingOrder); + void setSortingEnabled(bool enable); + bool isSortingEnabled() const; + + void editItem(QListWidgetItem *item); + void openPersistentEditor(QListWidgetItem *item); + void closePersistentEditor(QListWidgetItem *item); + + QWidget *itemWidget(QListWidgetItem *item) const; + void setItemWidget(QListWidgetItem *item, QWidget *widget); + inline void removeItemWidget(QListWidgetItem *item); + + bool isItemSelected(const QListWidgetItem *item) const; + void setItemSelected(const QListWidgetItem *item, bool select); + QList<QListWidgetItem*> selectedItems() const; + QList<QListWidgetItem*> findItems(const QString &text, Qt::MatchFlags flags) const; + + bool isItemHidden(const QListWidgetItem *item) const; + void setItemHidden(const QListWidgetItem *item, bool hide); + void dropEvent(QDropEvent *event); + +public Q_SLOTS: + void scrollToItem(const QListWidgetItem *item, QAbstractItemView::ScrollHint hint = EnsureVisible); + void clear(); + +Q_SIGNALS: + void itemPressed(QListWidgetItem *item); + void itemClicked(QListWidgetItem *item); + void itemDoubleClicked(QListWidgetItem *item); + void itemActivated(QListWidgetItem *item); + void itemEntered(QListWidgetItem *item); + void itemChanged(QListWidgetItem *item); + + void currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); + void currentTextChanged(const QString ¤tText); + void currentRowChanged(int currentRow); + + void itemSelectionChanged(); + +protected: + bool event(QEvent *e); + virtual QStringList mimeTypes() const; + virtual QMimeData *mimeData(const QList<QListWidgetItem*> items) const; +#ifndef QT_NO_DRAGANDDROP + virtual bool dropMimeData(int index, const QMimeData *data, Qt::DropAction action); + virtual Qt::DropActions supportedDropActions() const; +#endif + QList<QListWidgetItem*> items(const QMimeData *data) const; + + QModelIndex indexFromItem(QListWidgetItem *item) const; + QListWidgetItem *itemFromIndex(const QModelIndex &index) const; + +private: + void setModel(QAbstractItemModel *model); + Qt::SortOrder sortOrder() const; + + Q_DECLARE_PRIVATE(QListWidget) + Q_DISABLE_COPY(QListWidget) + + Q_PRIVATE_SLOT(d_func(), void _q_emitItemPressed(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemClicked(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemDoubleClicked(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemActivated(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemEntered(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemChanged(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitCurrentItemChanged(const QModelIndex &previous, const QModelIndex ¤t)) + Q_PRIVATE_SLOT(d_func(), void _q_sort()) + Q_PRIVATE_SLOT(d_func(), void _q_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)) +}; + +inline void QListWidget::removeItemWidget(QListWidgetItem *aItem) +{ setItemWidget(aItem, 0); } + +inline void QListWidget::addItem(QListWidgetItem *aitem) +{ insertItem(count(), aitem); } + +inline QListWidgetItem *QListWidget::itemAt(int ax, int ay) const +{ return itemAt(QPoint(ax, ay)); } + +inline void QListWidgetItem::setSelected(bool aselect) +{ if (view) view->setItemSelected(this, aselect); } + +inline bool QListWidgetItem::isSelected() const +{ return (view ? view->isItemSelected(this) : false); } + +inline void QListWidgetItem::setHidden(bool ahide) +{ if (view) view->setItemHidden(this, ahide); } + +inline bool QListWidgetItem::isHidden() const +{ return (view ? view->isItemHidden(this) : false); } + +#endif // QT_NO_LISTWIDGET + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QLISTWIDGET_H diff --git a/src/gui/itemviews/qlistwidget_p.h b/src/gui/itemviews/qlistwidget_p.h new file mode 100644 index 0000000..8556047 --- /dev/null +++ b/src/gui/itemviews/qlistwidget_p.h @@ -0,0 +1,175 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLISTWIDGET_P_H +#define QLISTWIDGET_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. This header file may change +// from version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qabstractitemmodel.h> +#include <QtGui/qabstractitemview.h> +#include <QtGui/qlistwidget.h> +#include <qitemdelegate.h> +#include <private/qlistview_p.h> +#include <private/qwidgetitemdata_p.h> + +#ifndef QT_NO_LISTWIDGET + +QT_BEGIN_NAMESPACE + +class QListModelLessThan +{ +public: + inline bool operator()(QListWidgetItem *i1, QListWidgetItem *i2) const + { return *i1 < *i2; } +}; + +class QListModelGreaterThan +{ +public: + inline bool operator()(QListWidgetItem *i1, QListWidgetItem *i2) const + { return *i2 < *i1; } +}; + +class QListModel : public QAbstractListModel +{ + Q_OBJECT +public: + QListModel(QListWidget *parent); + ~QListModel(); + + void clear(); + QListWidgetItem *at(int row) const; + void insert(int row, QListWidgetItem *item); + void insert(int row, const QStringList &items); + void remove(QListWidgetItem *item); + QListWidgetItem *take(int row); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + + QModelIndex index(QListWidgetItem *item) const; + QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex &index, const QVariant &value, int role); + + QMap<int, QVariant> itemData(const QModelIndex &index) const; + + bool insertRows(int row, int count = 1, const QModelIndex &parent = QModelIndex()); + bool removeRows(int row, int count = 1, const QModelIndex &parent = QModelIndex()); + + Qt::ItemFlags flags(const QModelIndex &index) const; + + void sort(int column, Qt::SortOrder order); + void ensureSorted(int column, Qt::SortOrder order, int start, int end); + static bool itemLessThan(const QPair<QListWidgetItem*,int> &left, + const QPair<QListWidgetItem*,int> &right); + static bool itemGreaterThan(const QPair<QListWidgetItem*,int> &left, + const QPair<QListWidgetItem*,int> &right); + static QList<QListWidgetItem*>::iterator sortedInsertionIterator( + const QList<QListWidgetItem*>::iterator &begin, + const QList<QListWidgetItem*>::iterator &end, + Qt::SortOrder order, QListWidgetItem *item); + + void itemChanged(QListWidgetItem *item); + + // dnd + QStringList mimeTypes() const; + QMimeData *mimeData(const QModelIndexList &indexes) const; +#ifndef QT_NO_DRAGANDDROP + bool dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent); + Qt::DropActions supportedDropActions() const; +#endif + + QMimeData *internalMimeData() const; +private: + QList<QListWidgetItem*> items; + + // A cache must be mutable if get-functions should have const modifiers + mutable QModelIndexList cachedIndexes; +}; + + + +class QListWidgetPrivate : public QListViewPrivate +{ + Q_DECLARE_PUBLIC(QListWidget) +public: + QListWidgetPrivate() : QListViewPrivate(), sortOrder(Qt::AscendingOrder), sortingEnabled(false) {} + inline QListModel *model() const { return qobject_cast<QListModel*>(q_func()->model()); } + void setup(); + void _q_emitItemPressed(const QModelIndex &index); + void _q_emitItemClicked(const QModelIndex &index); + void _q_emitItemDoubleClicked(const QModelIndex &index); + void _q_emitItemActivated(const QModelIndex &index); + void _q_emitItemEntered(const QModelIndex &index); + void _q_emitItemChanged(const QModelIndex &index); + void _q_emitCurrentItemChanged(const QModelIndex ¤t, const QModelIndex &previous); + void _q_sort(); + void _q_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + Qt::SortOrder sortOrder; + bool sortingEnabled; +}; + +class QListWidgetItemPrivate +{ +public: + QListWidgetItemPrivate(QListWidgetItem *item) : q(item), theid(-1) {} + QListWidgetItem *q; + int id; + QVector<QWidgetItemData> values; + int theid; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_LISTWIDGET + +#endif // QLISTWIDGET_P_H diff --git a/src/gui/itemviews/qproxymodel.cpp b/src/gui/itemviews/qproxymodel.cpp new file mode 100644 index 0000000..1be5eff --- /dev/null +++ b/src/gui/itemviews/qproxymodel.cpp @@ -0,0 +1,547 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qproxymodel.h" + +#ifndef QT_NO_PROXYMODEL +#include <private/qproxymodel_p.h> +#include <qsize.h> +#include <qstringlist.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QProxyModel + \obsolete + \brief The QProxyModel class provides support for processing data + passed between another model and a view. + + \ingroup model-view + + If you want to do filtering and sorting, see QSortFilterProxyModel. + + Proxy models provide a standard model interface that can be used to + manipulate the data retrieved through an underlying model. They can be used to + perform operations such as sorting and filtering on the data obtained without + changing the contents of the model. + + Just as with subclasses of QAbstractItemView, QProxyModel provides the setModel() + function that is used to specify the model to be acted on by the proxy. + Views can be connected to either the underlying model or the proxy model with + \l QAbstractItemView::setModel(). + + Since views rely on the information provided in model indexes to identify + items of data from models, and to position these items in some visual + representation, proxy models must create their own model indexes instead of + supplying model indexes from their underlying models. + + \sa \link model-view-programming.html Model/View Programming\endlink QAbstractItemModel + +*/ + +/*! + Constructs a proxy model with the given \a parent. +*/ + +QProxyModel::QProxyModel(QObject *parent) + : QAbstractItemModel(*new QProxyModelPrivate, parent) +{ + Q_D(QProxyModel); + setModel(&d->empty); +} + +/*! + \internal +*/ +QProxyModel::QProxyModel(QProxyModelPrivate &dd, QObject *parent) + : QAbstractItemModel(dd, parent) +{ + Q_D(QProxyModel); + setModel(&d->empty); +} + +/*! + Destroys the proxy model. +*/ +QProxyModel::~QProxyModel() +{ +} + +/*! + Sets the given \a model to be processed by the proxy model. +*/ +void QProxyModel::setModel(QAbstractItemModel *model) +{ + Q_D(QProxyModel); + if (d->model && d->model != &d->empty) + disconnectFromModel(d->model); + if (model) { + d->model = model; + connectToModel(model); + } else { + d->model = &d->empty; + } +} + +/*! + Returns the model that contains the data that is available through the + proxy model. +*/ +QAbstractItemModel *QProxyModel::model() const +{ + Q_D(const QProxyModel); + return d->model; +} + +/*! + Returns the model index with the given \a row, \a column, and \a parent. + + \sa QAbstractItemModel::index() +*/ +QModelIndex QProxyModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_D(const QProxyModel); + return setProxyModel(d->model->index(row, column, setSourceModel(parent))); +} + +/*! + Returns the model index that corresponds to the parent of the given \a child + index. +*/ +QModelIndex QProxyModel::parent(const QModelIndex &child) const +{ + Q_D(const QProxyModel); + return setProxyModel(d->model->parent(setSourceModel(child))); +} + +/*! + Returns the number of rows for the given \a parent. + + \sa QAbstractItemModel::rowCount() +*/ +int QProxyModel::rowCount(const QModelIndex &parent) const +{ + Q_D(const QProxyModel); + return d->model->rowCount(setSourceModel(parent)); +} + +/*! + Returns the number of columns for the given \a parent. + + \sa QAbstractItemModel::columnCount() +*/ +int QProxyModel::columnCount(const QModelIndex &parent) const +{ + Q_D(const QProxyModel); + return d->model->columnCount(setSourceModel(parent)); +} + +/*! + Returns true if the item corresponding to the \a parent index has child + items; otherwise returns false. + + \sa QAbstractItemModel::hasChildren() +*/ +bool QProxyModel::hasChildren(const QModelIndex &parent) const +{ + Q_D(const QProxyModel); + return d->model->hasChildren(setSourceModel(parent)); +} + +/*! + Returns the data stored in the item with the given \a index under the + specified \a role. +*/ +QVariant QProxyModel::data(const QModelIndex &index, int role) const +{ + Q_D(const QProxyModel); + return d->model->data(setSourceModel(index), role); +} + +/*! + Sets the \a role data for the item at \a index to \a value. + Returns true if successful; otherwise returns false. + + The base class implementation returns false. This function and + data() must be reimplemented for editable models. + + \sa data() itemData() QAbstractItemModel::setData() +*/ +bool QProxyModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + Q_D(const QProxyModel); + return d->model->setData(setSourceModel(index), value, role); +} + +/*! + Returns the data stored in the \a section of the header with specified + \a orientation under the given \a role. +*/ +QVariant QProxyModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_D(const QProxyModel); + return d->model->headerData(section, orientation, role); +} + +/*! + Sets the \a role data in the \a section of the header with the specified + \a orientation to the \a value given. + + \sa QAbstractItemModel::setHeaderData() +*/ +bool QProxyModel::setHeaderData(int section, Qt::Orientation orientation, + const QVariant &value, int role) +{ + Q_D(const QProxyModel); + return d->model->setHeaderData(section, orientation, value, role); +} + +/*! + Returns a list of MIME types that are supported by the model. +*/ +QStringList QProxyModel::mimeTypes() const +{ + Q_D(const QProxyModel); + return d->model->mimeTypes(); +} + +/*! + Returns MIME data for the specified \a indexes in the model. +*/ +QMimeData *QProxyModel::mimeData(const QModelIndexList &indexes) const +{ + Q_D(const QProxyModel); + QModelIndexList lst; + for (int i = 0; i < indexes.count(); ++i) + lst.append(setSourceModel(indexes.at(i))); + return d->model->mimeData(lst); +} + +/*! + Returns true if the model accepts the \a data dropped onto an attached + view for the specified \a action; otherwise returns false. + + The \a parent, \a row, and \a column details can be used to control + which MIME types are acceptable to different parts of a model when + received via the drag and drop system. +*/ +bool QProxyModel::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent) +{ + Q_D(const QProxyModel); + return d->model->dropMimeData(data, action, row, column, setSourceModel(parent)); +} + +/*! + Returns the drop actions that are supported by the model; this is + a combination of the individual actions defined in \l Qt::DropActions. + + The selection of drop actions provided by the model will influence the + behavior of the component that started the drag and drop operation. + + \sa \link dnd.html Drag and Drop\endlink +*/ +Qt::DropActions QProxyModel::supportedDropActions() const +{ + Q_D(const QProxyModel); + return d->model->supportedDropActions(); +} + +/*! + Inserts \a count rows into the model, creating new items as children of + the given \a parent. The new rows are inserted before the \a row + specified. If the \a parent item has no children, a single column is + created to contain the required number of rows. + + Returns true if the rows were successfully inserted; otherwise + returns false. + + \sa QAbstractItemModel::insertRows()*/ +bool QProxyModel::insertRows(int row, int count, const QModelIndex &parent) +{ + Q_D(const QProxyModel); + return d->model->insertRows(row, count, setSourceModel(parent)); +} + +/*! + Inserts \a count columns into the model, creating new items as children of + the given \a parent. The new columns are inserted before the \a column + specified. If the \a parent item has no children, a single row is created + to contain the required number of columns. + + Returns true if the columns were successfully inserted; otherwise + returns false. + + \sa QAbstractItemModel::insertColumns() +*/ +bool QProxyModel::insertColumns(int column, int count, const QModelIndex &parent) +{ + Q_D(const QProxyModel); + return d->model->insertColumns(column, count, setSourceModel(parent)); +} + +/*! + Fetches more child items of the given \a parent. This function is used by views + to tell the model that they can display more data than the model has provided. + + \sa QAbstractItemModel::fetchMore() +*/ +void QProxyModel::fetchMore(const QModelIndex &parent) +{ + Q_D(const QProxyModel); + d->model->fetchMore(parent); +} + +/*! + Returns the item flags for the given \a index. +*/ +Qt::ItemFlags QProxyModel::flags(const QModelIndex &index) const +{ + Q_D(const QProxyModel); + return d->model->flags(setSourceModel(index)); +} + +/*! + Sorts the child items in the specified \a column according to the sort + order defined by \a order. + + \sa QAbstractItemModel::sort() +*/ +void QProxyModel::sort(int column, Qt::SortOrder order) +{ + Q_D(QProxyModel); + d->model->sort(column, order); +} + +/*! + Returns a list of model indexes that each contain the given \a value for + the \a role specified. The search begins at the \a start index and is + performed according to the specified \a flags. The search continues until + the number of matching data items equals \a hits, the last row is reached, + or the search reaches \a start again, depending on whether \c MatchWrap is + specified in \a flags. + + \sa QAbstractItemModel::match() +*/ +QModelIndexList QProxyModel::match(const QModelIndex &start, + int role, const QVariant &value, + int hits, Qt::MatchFlags flags) const +{ + Q_D(const QProxyModel); + return d->model->match(start, role, value, hits, flags); +} + +/*! + Returns the size of the item that corresponds to the specified \a index. +*/ +QSize QProxyModel::span(const QModelIndex &index) const +{ + Q_D(const QProxyModel); + return d->model->span(setSourceModel(index)); +} + +/*! + */ +bool QProxyModel::submit() +{ + Q_D(QProxyModel); + return d->model->submit(); +} + +/*! + */ +void QProxyModel::revert() +{ + Q_D(QProxyModel); + d->model->revert(); +} + +/*! + \internal + Change the model pointer in the given \a source_index to point to the proxy model. + */ +QModelIndex QProxyModel::setProxyModel(const QModelIndex &source_index) const +{ + QModelIndex proxy_index = source_index; + if (proxy_index.isValid()) + proxy_index.m = this; + return proxy_index; +} + +/*! + \internal + Change the model pointer in the given \a proxy_index to point to the source model. + */ +QModelIndex QProxyModel::setSourceModel(const QModelIndex &proxy_index) const +{ + Q_D(const QProxyModel); + QModelIndex source_index = proxy_index; + source_index.m = d->model; + return source_index; +} + +/*! + \internal + Connect to all the signals emitted by given \a model. +*/ +void QProxyModel::connectToModel(const QAbstractItemModel *model) const +{ + connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_sourceDataChanged(QModelIndex,QModelIndex))); + connect(model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), + this, SIGNAL(headerDataChanged(Qt::Orientation,int,int))); // signal to signal + connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsAboutToBeInserted(QModelIndex,int,int))); + connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsInserted(QModelIndex,int,int))); + connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsAboutToBeRemoved(QModelIndex,int,int))); + connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsRemoved(QModelIndex,int,int))); + connect(model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsAboutToBeInserted(QModelIndex,int,int))); + connect(model, SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsInserted(QModelIndex,int,int))); + connect(model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsAboutToBeRemoved(QModelIndex,int,int))); + connect(model, SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsRemoved(QModelIndex,int,int))); + connect(model, SIGNAL(modelReset()), this, SIGNAL(modelReset())); // signal to signal + connect(model, SIGNAL(layoutAboutToBeChanged()), this, SIGNAL(layoutAboutToBeChanged())); // signal to signal + connect(model, SIGNAL(layoutChanged()), this, SIGNAL(layoutChanged())); // signal to signal +} + +/*! + \internal + Disconnect from all the signals emitted by the given \a model. + */ +void QProxyModel::disconnectFromModel(const QAbstractItemModel *model) const +{ + disconnect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_sourceDataChanged(QModelIndex,QModelIndex))); + disconnect(model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), + this, SIGNAL(headerDataChanged(Qt::Orientation,int,int))); // signal to signal + disconnect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsAboutToBeInserted(QModelIndex,int,int))); + disconnect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(rowsInserted(QModelIndex,int,int))); + disconnect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsAboutToBeRemoved(QModelIndex,int,int))); + disconnect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsRemoved(QModelIndex,int,int))); + disconnect(model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsAboutToBeInserted(QModelIndex,int,int))); + disconnect(model, SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsInserted(QModelIndex,int,int))); + disconnect(model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsAboutToBeRemoved(QModelIndex,int,int))); + disconnect(model, SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsRemoved(QModelIndex,int,int))); + disconnect(model, SIGNAL(modelReset()), this, SIGNAL(modelReset())); // signal to signal + disconnect(model, SIGNAL(layoutAboutToBeChanged()), this, SIGNAL(layoutAboutToBeChanged())); // signal to signal + disconnect(model, SIGNAL(layoutChanged()), this, SIGNAL(layoutChanged())); // signal to signal +} + +/*! + \fn QObject *QProxyModel::parent() const + \internal +*/ + +void QProxyModelPrivate::_q_sourceDataChanged(const QModelIndex &tl,const QModelIndex &br) +{ + Q_Q(QProxyModel); + emit q->dataChanged(q->setProxyModel(tl), q->setProxyModel(br)); +} + +void QProxyModelPrivate::_q_sourceRowsAboutToBeInserted(const QModelIndex &parent, int first ,int last) +{ + Q_Q(QProxyModel); + q->beginInsertRows(q->setProxyModel(parent), first, last); +} + +void QProxyModelPrivate::_q_sourceRowsInserted(const QModelIndex &, int, int) +{ + Q_Q(QProxyModel); + q->endInsertRows(); +} + +void QProxyModelPrivate::_q_sourceRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last) +{ + Q_Q(QProxyModel); + q->beginRemoveRows(q->setProxyModel(parent), first, last); +} + +void QProxyModelPrivate::_q_sourceRowsRemoved(const QModelIndex &, int, int) +{ + Q_Q(QProxyModel); + q->endRemoveRows(); +} + +void QProxyModelPrivate::_q_sourceColumnsAboutToBeInserted(const QModelIndex &parent, int first, int last) +{ + Q_Q(QProxyModel); + q->beginInsertColumns(q->setProxyModel(parent), first, last); +} + +void QProxyModelPrivate::_q_sourceColumnsInserted(const QModelIndex &, int, int) +{ + Q_Q(QProxyModel); + q->endInsertColumns(); +} + +void QProxyModelPrivate::_q_sourceColumnsAboutToBeRemoved(const QModelIndex &parent, int first, int last) +{ + Q_Q(QProxyModel); + q->beginRemoveColumns(q->setProxyModel(parent), first, last); +} + + +void QProxyModelPrivate::_q_sourceColumnsRemoved(const QModelIndex &, int, int) +{ + Q_Q(QProxyModel); + q->endRemoveColumns(); +} + +QT_END_NAMESPACE + +#include "moc_qproxymodel.cpp" + +#endif // QT_NO_PROXYMODEL diff --git a/src/gui/itemviews/qproxymodel.h b/src/gui/itemviews/qproxymodel.h new file mode 100644 index 0000000..0644962 --- /dev/null +++ b/src/gui/itemviews/qproxymodel.h @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPROXYMODEL_H +#define QPROXYMODEL_H + +#include <QtCore/qabstractitemmodel.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_PROXYMODEL + +class QProxyModelPrivate; + +class Q_GUI_EXPORT QProxyModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + explicit QProxyModel(QObject *parent = 0); + ~QProxyModel(); + + virtual void setModel(QAbstractItemModel *model); + QAbstractItemModel *model() const; + + // implementing model interface + + QModelIndex index(int row, int column, const QModelIndex &parent) const; + QModelIndex parent(const QModelIndex &child) const; + + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent) const; + bool hasChildren(const QModelIndex &parent) const; + + QVariant data(const QModelIndex &index, int role) const; + bool setData(const QModelIndex &index, const QVariant &value, int role); + + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, + int role); + + QStringList mimeTypes() const; + QMimeData *mimeData(const QModelIndexList &indexes) const; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent); + Qt::DropActions supportedDropActions() const; + + bool insertRows(int row, int count, const QModelIndex &parent); + bool insertColumns(int column, int count, const QModelIndex &parent); + + void fetchMore(const QModelIndex &parent); + Qt::ItemFlags flags(const QModelIndex &index) const; + + void sort(int column, Qt::SortOrder order); + + QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, + int hits, Qt::MatchFlags flags) const; + + QSize span(const QModelIndex &index) const; + + bool submit(); + void revert(); + +#ifdef Q_NO_USING_KEYWORD + inline QObject *parent() const { return QObject::parent(); } +#else + using QObject::parent; +#endif + +protected: + QProxyModel(QProxyModelPrivate &, QObject *parent = 0); + + QModelIndex setProxyModel(const QModelIndex &source_index) const; + QModelIndex setSourceModel(const QModelIndex &proxy_index) const; + + void connectToModel(const QAbstractItemModel *model) const; + void disconnectFromModel(const QAbstractItemModel *model) const; + +private: + Q_DECLARE_PRIVATE(QProxyModel) + Q_DISABLE_COPY(QProxyModel) + + Q_PRIVATE_SLOT(d_func(), void _q_sourceDataChanged(const QModelIndex&,const QModelIndex&)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsAboutToBeInserted(const QModelIndex&,int,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsInserted(const QModelIndex&,int,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsAboutToBeRemoved(const QModelIndex&,int,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsRemoved(const QModelIndex&,int,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsAboutToBeInserted(const QModelIndex&,int,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsInserted(const QModelIndex&,int,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsAboutToBeRemoved(const QModelIndex&,int,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsRemoved(const QModelIndex&,int,int)) +}; + +#endif // QT_NO_PROXYMODEL + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPROXYMODEL_H diff --git a/src/gui/itemviews/qproxymodel_p.h b/src/gui/itemviews/qproxymodel_p.h new file mode 100644 index 0000000..bfca274 --- /dev/null +++ b/src/gui/itemviews/qproxymodel_p.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPROXYMODEL_P_H +#define QPROXYMODEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of QAbstractItemModel*. This header file may change from version +// to version without notice, or even be removed. +// +// We mean it. +// +// + +#include "QtCore/qabstractitemmodel.h" +#include "private/qabstractitemmodel_p.h" + +#ifndef QT_NO_PROXYMODEL + +QT_BEGIN_NAMESPACE + +class QEmptyModel : public QAbstractItemModel +{ +public: + explicit QEmptyModel(QObject *parent = 0) : QAbstractItemModel(parent) {} + QModelIndex index(int, int, const QModelIndex &) const { return QModelIndex(); } + QModelIndex parent(const QModelIndex &) const { return QModelIndex(); } + int rowCount(const QModelIndex &) const { return 0; } + int columnCount(const QModelIndex &) const { return 0; } + bool hasChildren(const QModelIndex &) const { return false; } + QVariant data(const QModelIndex &, int) const { return QVariant(); } +}; + +class QProxyModelPrivate : private QAbstractItemModelPrivate +{ + Q_DECLARE_PUBLIC(QProxyModel) + +public: + void _q_sourceDataChanged(const QModelIndex &tl,const QModelIndex &br); + void _q_sourceRowsAboutToBeInserted(const QModelIndex &parent, int first ,int last); + void _q_sourceRowsInserted(const QModelIndex &parent, int first ,int last); + void _q_sourceRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last); + void _q_sourceRowsRemoved(const QModelIndex &parent, int first, int last); + void _q_sourceColumnsAboutToBeInserted(const QModelIndex &parent, int first, int last); + void _q_sourceColumnsInserted(const QModelIndex &parent, int first, int last); + void _q_sourceColumnsAboutToBeRemoved(const QModelIndex &parent, int first, int last); + void _q_sourceColumnsRemoved(const QModelIndex &parent, int first, int last); + + QProxyModelPrivate() : QAbstractItemModelPrivate(), model(0) {} + QAbstractItemModel *model; + QEmptyModel empty; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_PROXYMODEL + +#endif // QPROXYMODEL_P_H diff --git a/src/gui/itemviews/qsortfilterproxymodel.cpp b/src/gui/itemviews/qsortfilterproxymodel.cpp new file mode 100644 index 0000000..b3993c7 --- /dev/null +++ b/src/gui/itemviews/qsortfilterproxymodel.cpp @@ -0,0 +1,2392 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsortfilterproxymodel.h" + +#ifndef QT_NO_SORTFILTERPROXYMODEL + +#include "qitemselectionmodel.h" +#include <qsize.h> +#include <qdebug.h> +#include <qdatetime.h> +#include <qpair.h> +#include <qstringlist.h> +#include <private/qabstractitemmodel_p.h> +#include <private/qabstractproxymodel_p.h> + +QT_BEGIN_NAMESPACE + +typedef QList<QPair<QModelIndex, QPersistentModelIndex> > QModelIndexPairList; + +class QSortFilterProxyModelLessThan +{ +public: + inline QSortFilterProxyModelLessThan(int column, const QModelIndex &parent, + const QAbstractItemModel *source, + const QSortFilterProxyModel *proxy) + : sort_column(column), source_parent(parent), source_model(source), proxy_model(proxy) {} + + inline bool operator()(int r1, int r2) const + { + QModelIndex i1 = source_model->index(r1, sort_column, source_parent); + QModelIndex i2 = source_model->index(r2, sort_column, source_parent); + return proxy_model->lessThan(i1, i2); + } + +private: + int sort_column; + QModelIndex source_parent; + const QAbstractItemModel *source_model; + const QSortFilterProxyModel *proxy_model; +}; + +class QSortFilterProxyModelGreaterThan +{ +public: + inline QSortFilterProxyModelGreaterThan(int column, const QModelIndex &parent, + const QAbstractItemModel *source, + const QSortFilterProxyModel *proxy) + : sort_column(column), source_parent(parent), + source_model(source), proxy_model(proxy) {} + + inline bool operator()(int r1, int r2) const + { + QModelIndex i1 = source_model->index(r1, sort_column, source_parent); + QModelIndex i2 = source_model->index(r2, sort_column, source_parent); + return proxy_model->lessThan(i2, i1); + } + +private: + int sort_column; + QModelIndex source_parent; + const QAbstractItemModel *source_model; + const QSortFilterProxyModel *proxy_model; +}; + + +class QSortFilterProxyModelPrivate : public QAbstractProxyModelPrivate +{ + Q_DECLARE_PUBLIC(QSortFilterProxyModel) + +public: + struct Mapping { + QVector<int> source_rows; + QVector<int> source_columns; + QVector<int> proxy_rows; + QVector<int> proxy_columns; + QVector<QModelIndex> mapped_children; + QMap<QModelIndex, Mapping *>::const_iterator map_iter; + }; + + mutable QMap<QModelIndex, Mapping*> source_index_mapping; + + int source_sort_column; + int proxy_sort_column; + Qt::SortOrder sort_order; + Qt::CaseSensitivity sort_casesensitivity; + int sort_role; + bool sort_localeaware; + + int filter_column; + QRegExp filter_regexp; + int filter_role; + + bool dynamic_sortfilter; + + QModelIndexPairList saved_persistent_indexes; + + QMap<QModelIndex, Mapping *>::const_iterator create_mapping( + const QModelIndex &source_parent) const; + QModelIndex proxy_to_source(const QModelIndex &proxyIndex) const; + QModelIndex source_to_proxy(const QModelIndex &sourceIndex) const; + + void remove_from_mapping(const QModelIndex &source_parent); + + inline QMap<QModelIndex, Mapping *>::const_iterator index_to_iterator( + const QModelIndex &proxy_index) const + { + Q_ASSERT(proxy_index.isValid()); + const void *p = proxy_index.internalPointer(); + Q_ASSERT(p); + QMap<QModelIndex, Mapping *>::const_iterator it = + static_cast<const Mapping*>(p)->map_iter; + Q_ASSERT(it != source_index_mapping.constEnd()); + Q_ASSERT(it.value()); + return it; + } + + inline QModelIndex create_index(int row, int column, + QMap<QModelIndex, Mapping*>::const_iterator it) const + { + return q_func()->createIndex(row, column, *it); + } + + void _q_sourceDataChanged(const QModelIndex &source_top_left, + const QModelIndex &source_bottom_right); + void _q_sourceHeaderDataChanged(Qt::Orientation orientation, int start, int end); + + void _q_sourceReset(); + + void _q_sourceLayoutAboutToBeChanged(); + void _q_sourceLayoutChanged(); + + void _q_sourceRowsAboutToBeInserted(const QModelIndex &source_parent, + int start, int end); + void _q_sourceRowsInserted(const QModelIndex &source_parent, + int start, int end); + void _q_sourceRowsAboutToBeRemoved(const QModelIndex &source_parent, + int start, int end); + void _q_sourceRowsRemoved(const QModelIndex &source_parent, + int start, int end); + void _q_sourceColumnsAboutToBeInserted(const QModelIndex &source_parent, + int start, int end); + void _q_sourceColumnsInserted(const QModelIndex &source_parent, + int start, int end); + void _q_sourceColumnsAboutToBeRemoved(const QModelIndex &source_parent, + int start, int end); + void _q_sourceColumnsRemoved(const QModelIndex &source_parent, + int start, int end); + + void clear_mapping(); + + void sort(); + bool update_source_sort_column(); + void sort_source_rows(QVector<int> &source_rows, + const QModelIndex &source_parent) const; + QVector<QPair<int, QVector<int > > > proxy_intervals_for_source_items_to_add( + const QVector<int> &proxy_to_source, const QVector<int> &source_items, + const QModelIndex &source_parent, Qt::Orientation orient) const; + QVector<QPair<int, int > > proxy_intervals_for_source_items( + const QVector<int> &source_to_proxy, const QVector<int> &source_items) const; + void insert_source_items( + QVector<int> &source_to_proxy, QVector<int> &proxy_to_source, + const QVector<int> &source_items, const QModelIndex &source_parent, + Qt::Orientation orient, bool emit_signal = true); + void remove_source_items( + QVector<int> &source_to_proxy, QVector<int> &proxy_to_source, + const QVector<int> &source_items, const QModelIndex &source_parent, + Qt::Orientation orient, bool emit_signal = true); + void remove_proxy_interval( + QVector<int> &source_to_proxy, QVector<int> &proxy_to_source, + int proxy_start, int proxy_end, const QModelIndex &proxy_parent, + Qt::Orientation orient, bool emit_signal = true); + void build_source_to_proxy_mapping( + const QVector<int> &proxy_to_source, QVector<int> &source_to_proxy) const; + void source_items_inserted(const QModelIndex &source_parent, + int start, int end, Qt::Orientation orient); + void source_items_about_to_be_removed(const QModelIndex &source_parent, + int start, int end, Qt::Orientation orient); + void source_items_removed(const QModelIndex &source_parent, + int start, int end, Qt::Orientation orient); + void proxy_item_range( + const QVector<int> &source_to_proxy, const QVector<int> &source_items, + int &proxy_low, int &proxy_high) const; + + QModelIndexPairList store_persistent_indexes(); + void update_persistent_indexes(const QModelIndexPairList &source_indexes); + + void filter_changed(); + void handle_filter_changed( + QVector<int> &source_to_proxy, QVector<int> &proxy_to_source, + const QModelIndex &source_parent, Qt::Orientation orient); + + void updateChildrenMapping(const QModelIndex &source_parent, Mapping *parent_mapping, + Qt::Orientation orient, int start, int end, int delta_item_count, bool remove); + + virtual void _q_sourceModelDestroyed(); +}; + +typedef QMap<QModelIndex, QSortFilterProxyModelPrivate::Mapping *> IndexMap; + +void QSortFilterProxyModelPrivate::_q_sourceModelDestroyed() +{ + QAbstractProxyModelPrivate::_q_sourceModelDestroyed(); + clear_mapping(); +} + +void QSortFilterProxyModelPrivate::remove_from_mapping(const QModelIndex &source_parent) +{ + if (Mapping *m = source_index_mapping.take(source_parent)) { + for (int i = 0; i < m->mapped_children.size(); ++i) + remove_from_mapping(m->mapped_children.at(i)); + delete m; + } +} + +void QSortFilterProxyModelPrivate::clear_mapping() +{ + // store the persistent indexes + QModelIndexPairList source_indexes = store_persistent_indexes(); + + qDeleteAll(source_index_mapping); + source_index_mapping.clear(); + + // update the persistent indexes + update_persistent_indexes(source_indexes); +} + +IndexMap::const_iterator QSortFilterProxyModelPrivate::create_mapping( + const QModelIndex &source_parent) const +{ + Q_Q(const QSortFilterProxyModel); + + IndexMap::const_iterator it = source_index_mapping.constFind(source_parent); + if (it != source_index_mapping.constEnd()) // was mapped already + return it; + + Mapping *m = new Mapping; + + int source_rows = model->rowCount(source_parent); + for (int i = 0; i < source_rows; ++i) { + if (q->filterAcceptsRow(i, source_parent)) + m->source_rows.append(i); + } + int source_cols = model->columnCount(source_parent); + for (int i = 0; i < source_cols; ++i) { + if (q->filterAcceptsColumn(i, source_parent)) + m->source_columns.append(i); + } + + sort_source_rows(m->source_rows, source_parent); + m->proxy_rows.resize(source_rows); + build_source_to_proxy_mapping(m->source_rows, m->proxy_rows); + m->proxy_columns.resize(source_cols); + build_source_to_proxy_mapping(m->source_columns, m->proxy_columns); + + it = IndexMap::const_iterator(source_index_mapping.insert(source_parent, m)); + m->map_iter = it; + + if (source_parent.isValid()) { + QModelIndex source_grand_parent = source_parent.parent(); + IndexMap::const_iterator it2 = create_mapping(source_grand_parent); + Q_ASSERT(it2 != source_index_mapping.constEnd()); + it2.value()->mapped_children.append(source_parent); + } + + Q_ASSERT(it != source_index_mapping.constEnd()); + Q_ASSERT(it.value()); + + return it; +} + +QModelIndex QSortFilterProxyModelPrivate::proxy_to_source(const QModelIndex &proxy_index) const +{ + if (!proxy_index.isValid()) + return QModelIndex(); // for now; we may want to be able to set a root index later + IndexMap::const_iterator it = index_to_iterator(proxy_index); + Mapping *m = it.value(); + if ((proxy_index.row() >= m->source_rows.size()) || (proxy_index.column() >= m->source_columns.size())) + return QModelIndex(); + int source_row = m->source_rows.at(proxy_index.row()); + int source_col = m->source_columns.at(proxy_index.column()); + return model->index(source_row, source_col, it.key()); +} + +QModelIndex QSortFilterProxyModelPrivate::source_to_proxy(const QModelIndex &source_index) const +{ + if (!source_index.isValid()) + return QModelIndex(); // for now; we may want to be able to set a root index later + QModelIndex source_parent = source_index.parent(); + IndexMap::const_iterator it = create_mapping(source_parent); + Mapping *m = it.value(); + if ((source_index.row() >= m->proxy_rows.size()) || (source_index.column() >= m->proxy_columns.size())) + return QModelIndex(); + int proxy_row = m->proxy_rows.at(source_index.row()); + int proxy_column = m->proxy_columns.at(source_index.column()); + if (proxy_row == -1 || proxy_column == -1) + return QModelIndex(); + return create_index(proxy_row, proxy_column, it); +} + +/*! + \internal + + Sorts the existing mappings. +*/ +void QSortFilterProxyModelPrivate::sort() +{ + Q_Q(QSortFilterProxyModel); + emit q->layoutAboutToBeChanged(); + QModelIndexPairList source_indexes = store_persistent_indexes(); + IndexMap::const_iterator it = source_index_mapping.constBegin(); + for (; it != source_index_mapping.constEnd(); ++it) { + QModelIndex source_parent = it.key(); + Mapping *m = it.value(); + sort_source_rows(m->source_rows, source_parent); + build_source_to_proxy_mapping(m->source_rows, m->proxy_rows); + } + update_persistent_indexes(source_indexes); + emit q->layoutChanged(); +} + +/*! + \internal + + update the source_sort_column according to the proxy_sort_column + return true if the column was changed +*/ +bool QSortFilterProxyModelPrivate::update_source_sort_column() +{ + Q_Q(QSortFilterProxyModel); + QModelIndex proxy_index = q->index(0, proxy_sort_column, QModelIndex()); + int old_source_sort_colum = source_sort_column; + source_sort_column = q->mapToSource(proxy_index).column(); + return old_source_sort_colum != source_sort_column; +} + + +/*! + \internal + + Sorts the given \a source_rows according to current sort column and order. +*/ +void QSortFilterProxyModelPrivate::sort_source_rows( + QVector<int> &source_rows, const QModelIndex &source_parent) const +{ + Q_Q(const QSortFilterProxyModel); + if (source_sort_column >= 0) { + if (sort_order == Qt::AscendingOrder) { + QSortFilterProxyModelLessThan lt(source_sort_column, source_parent, model, q); + qStableSort(source_rows.begin(), source_rows.end(), lt); + } else { + QSortFilterProxyModelGreaterThan gt(source_sort_column, source_parent, model, q); + qStableSort(source_rows.begin(), source_rows.end(), gt); + } + } else { // restore the source model order + qStableSort(source_rows.begin(), source_rows.end()); + } +} + +/*! + \internal + + Given source-to-proxy mapping \a source_to_proxy and the set of + source items \a source_items (which are part of that mapping), + determines the corresponding proxy item intervals that should + be removed from the proxy model. + + The result is a vector of pairs, where each pair represents a + (start, end) tuple, sorted in ascending order. +*/ +QVector<QPair<int, int > > QSortFilterProxyModelPrivate::proxy_intervals_for_source_items( + const QVector<int> &source_to_proxy, const QVector<int> &source_items) const +{ + QVector<QPair<int, int> > proxy_intervals; + if (source_items.isEmpty()) + return proxy_intervals; + + int source_items_index = 0; + while (source_items_index < source_items.size()) { + int first_proxy_item = source_to_proxy.at(source_items.at(source_items_index)); + Q_ASSERT(first_proxy_item != -1); + int last_proxy_item = first_proxy_item; + ++source_items_index; + // Find end of interval + while ((source_items_index < source_items.size()) + && (source_to_proxy.at(source_items.at(source_items_index)) == last_proxy_item + 1)) { + ++last_proxy_item; + ++source_items_index; + } + // Add interval to result + proxy_intervals.append(QPair<int, int>(first_proxy_item, last_proxy_item)); + } + qStableSort(proxy_intervals.begin(), proxy_intervals.end()); + return proxy_intervals; +} + +/*! + \internal + + Given source-to-proxy mapping \a src_to_proxy and proxy-to-source mapping + \a proxy_to_source, removes \a source_items from this proxy model. + The corresponding proxy items are removed in intervals, so that the proper + rows/columnsRemoved(start, end) signals will be generated. +*/ +void QSortFilterProxyModelPrivate::remove_source_items( + QVector<int> &source_to_proxy, QVector<int> &proxy_to_source, + const QVector<int> &source_items, const QModelIndex &source_parent, + Qt::Orientation orient, bool emit_signal) +{ + Q_Q(QSortFilterProxyModel); + QModelIndex proxy_parent = q->mapFromSource(source_parent); + if (!proxy_parent.isValid() && source_parent.isValid()) + return; // nothing to do (already removed) + + QVector<QPair<int, int> > proxy_intervals; + proxy_intervals = proxy_intervals_for_source_items(source_to_proxy, source_items); + + for (int i = proxy_intervals.size()-1; i >= 0; --i) { + QPair<int, int> interval = proxy_intervals.at(i); + int proxy_start = interval.first; + int proxy_end = interval.second; + remove_proxy_interval(source_to_proxy, proxy_to_source, proxy_start, proxy_end, + proxy_parent, orient, emit_signal); + } +} + +/*! + \internal + + Given source-to-proxy mapping \a source_to_proxy and proxy-to-source mapping + \a proxy_to_source, removes items from \a proxy_start to \a proxy_end + (inclusive) from this proxy model. +*/ +void QSortFilterProxyModelPrivate::remove_proxy_interval( + QVector<int> &source_to_proxy, QVector<int> &proxy_to_source, int proxy_start, int proxy_end, + const QModelIndex &proxy_parent, Qt::Orientation orient, bool emit_signal) +{ + Q_Q(QSortFilterProxyModel); + if (emit_signal) { + if (orient == Qt::Vertical) + q->beginRemoveRows(proxy_parent, proxy_start, proxy_end); + else + q->beginRemoveColumns(proxy_parent, proxy_start, proxy_end); + } + + // Remove items from proxy-to-source mapping + proxy_to_source.remove(proxy_start, proxy_end - proxy_start + 1); + + build_source_to_proxy_mapping(proxy_to_source, source_to_proxy); + + if (emit_signal) { + if (orient == Qt::Vertical) + q->endRemoveRows(); + else + q->endRemoveColumns(); + } +} + +/*! + \internal + + Given proxy-to-source mapping \a proxy_to_source and a set of + unmapped source items \a source_items, determines the proxy item + intervals at which the subsets of source items should be inserted + (but does not actually add them to the mapping). + + The result is a vector of pairs, each pair representing a tuple (start, + items), where items is a vector containing the (sorted) source items that + should be inserted at that proxy model location. +*/ +QVector<QPair<int, QVector<int > > > QSortFilterProxyModelPrivate::proxy_intervals_for_source_items_to_add( + const QVector<int> &proxy_to_source, const QVector<int> &source_items, + const QModelIndex &source_parent, Qt::Orientation orient) const +{ + Q_Q(const QSortFilterProxyModel); + QVector<QPair<int, QVector<int> > > proxy_intervals; + if (source_items.isEmpty()) + return proxy_intervals; + + int proxy_low = 0; + int proxy_item = 0; + int source_items_index = 0; + QVector<int> source_items_in_interval; + bool compare = (orient == Qt::Vertical && source_sort_column >= 0); + while (source_items_index < source_items.size()) { + source_items_in_interval.clear(); + int first_new_source_item = source_items.at(source_items_index); + source_items_in_interval.append(first_new_source_item); + ++source_items_index; + + // Find proxy item at which insertion should be started + int proxy_high = proxy_to_source.size() - 1; + QModelIndex i1 = compare ? model->index(first_new_source_item, source_sort_column, source_parent) : QModelIndex(); + while (proxy_low <= proxy_high) { + proxy_item = (proxy_low + proxy_high) / 2; + if (compare) { + QModelIndex i2 = model->index(proxy_to_source.at(proxy_item), source_sort_column, source_parent); + if ((sort_order == Qt::AscendingOrder) ? q->lessThan(i1, i2) : q->lessThan(i2, i1)) + proxy_high = proxy_item - 1; + else + proxy_low = proxy_item + 1; + } else { + if (first_new_source_item < proxy_to_source.at(proxy_item)) + proxy_high = proxy_item - 1; + else + proxy_low = proxy_item + 1; + } + } + proxy_item = proxy_low; + + // Find the sequence of new source items that should be inserted here + if (proxy_item >= proxy_to_source.size()) { + for ( ; source_items_index < source_items.size(); ++source_items_index) + source_items_in_interval.append(source_items.at(source_items_index)); + } else { + i1 = compare ? model->index(proxy_to_source.at(proxy_item), source_sort_column, source_parent) : QModelIndex(); + for ( ; source_items_index < source_items.size(); ++source_items_index) { + int new_source_item = source_items.at(source_items_index); + if (compare) { + QModelIndex i2 = model->index(new_source_item, source_sort_column, source_parent); + if ((sort_order == Qt::AscendingOrder) ? q->lessThan(i1, i2) : q->lessThan(i2, i1)) + break; + } else { + if (proxy_to_source.at(proxy_item) < new_source_item) + break; + } + source_items_in_interval.append(new_source_item); + } + } + + // Add interval to result + proxy_intervals.append(QPair<int, QVector<int> >(proxy_item, source_items_in_interval)); + } + return proxy_intervals; +} + +/*! + \internal + + Given source-to-proxy mapping \a source_to_proxy and proxy-to-source mapping + \a proxy_to_source, inserts the given \a source_items into this proxy model. + The source items are inserted in intervals (based on some sorted order), so + that the proper rows/columnsInserted(start, end) signals will be generated. +*/ +void QSortFilterProxyModelPrivate::insert_source_items( + QVector<int> &source_to_proxy, QVector<int> &proxy_to_source, + const QVector<int> &source_items, const QModelIndex &source_parent, + Qt::Orientation orient, bool emit_signal) +{ + Q_Q(QSortFilterProxyModel); + QModelIndex proxy_parent = q->mapFromSource(source_parent); + if (!proxy_parent.isValid() && source_parent.isValid()) + return; // nothing to do (source_parent is not mapped) + + QVector<QPair<int, QVector<int> > > proxy_intervals; + proxy_intervals = proxy_intervals_for_source_items_to_add( + proxy_to_source, source_items, source_parent, orient); + + for (int i = proxy_intervals.size()-1; i >= 0; --i) { + QPair<int, QVector<int> > interval = proxy_intervals.at(i); + int proxy_start = interval.first; + QVector<int> source_items = interval.second; + int proxy_end = proxy_start + source_items.size() - 1; + + if (emit_signal) { + if (orient == Qt::Vertical) + q->beginInsertRows(proxy_parent, proxy_start, proxy_end); + else + q->beginInsertColumns(proxy_parent, proxy_start, proxy_end); + } + + for (int i = 0; i < source_items.size(); ++i) + proxy_to_source.insert(proxy_start + i, source_items.at(i)); + + build_source_to_proxy_mapping(proxy_to_source, source_to_proxy); + + if (emit_signal) { + if (orient == Qt::Vertical) + q->endInsertRows(); + else + q->endInsertColumns(); + } + } +} + +/*! + \internal + + Handles source model items insertion (columnsInserted(), rowsInserted()). + Determines + 1) which of the inserted items to also insert into proxy model (filtering), + 2) where to insert the items into the proxy model (sorting), + then inserts those items. + The items are inserted into the proxy model in intervals (based on + sorted order), so that the proper rows/columnsInserted(start, end) + signals will be generated. +*/ +void QSortFilterProxyModelPrivate::source_items_inserted( + const QModelIndex &source_parent, int start, int end, Qt::Orientation orient) +{ + Q_Q(QSortFilterProxyModel); + if ((start < 0) || (end < 0)) + return; + IndexMap::const_iterator it = source_index_mapping.constFind(source_parent); + if (it == source_index_mapping.constEnd()) { + if (source_parent.isValid()) { + QModelIndex source_grand_parent = source_parent.parent(); + it = source_index_mapping.constFind(source_grand_parent); + if (it == source_index_mapping.constEnd()) { + // Don't care, since we don't have mapping for the grand parent + return; + } + Mapping *gm = it.value(); + if (gm->proxy_rows.at(source_parent.row()) == -1 || + gm->proxy_columns.at(source_parent.column()) == -1) { + // Don't care, since parent is filtered + return; + } + } + it = create_mapping(source_parent); + Mapping *m = it.value(); + QModelIndex proxy_parent = q->mapFromSource(source_parent); + if (m->source_rows.count() > 0) { + q->beginInsertRows(proxy_parent, 0, m->source_rows.count() - 1); + q->endInsertRows(); + } + if (m->source_columns.count() > 0) { + q->beginInsertColumns(proxy_parent, 0, m->source_columns.count() - 1); + q->endInsertColumns(); + } + return; + } + + Mapping *m = it.value(); + QVector<int> &source_to_proxy = (orient == Qt::Vertical) ? m->proxy_rows : m->proxy_columns; + QVector<int> &proxy_to_source = (orient == Qt::Vertical) ? m->source_rows : m->source_columns; + + int delta_item_count = end - start + 1; + int old_item_count = source_to_proxy.size(); + + updateChildrenMapping(source_parent, m, orient, start, end, delta_item_count, false); + + // Expand source-to-proxy mapping to account for new items + if (start < 0 || start > source_to_proxy.size()) { + qWarning("QSortFilterProxyModel: invalid inserted rows reported by source model"); + remove_from_mapping(source_parent); + return; + } + source_to_proxy.insert(start, delta_item_count, -1); + + if (start < old_item_count) { + // Adjust existing "stale" indexes in proxy-to-source mapping + int proxy_count = proxy_to_source.size(); + for (int proxy_item = 0; proxy_item < proxy_count; ++proxy_item) { + int source_item = proxy_to_source.at(proxy_item); + if (source_item >= start) + proxy_to_source.replace(proxy_item, source_item + delta_item_count); + } + build_source_to_proxy_mapping(proxy_to_source, source_to_proxy); + } + + // Figure out which items to add to mapping based on filter + QVector<int> source_items; + for (int i = start; i <= end; ++i) { + if ((orient == Qt::Vertical) + ? q->filterAcceptsRow(i, source_parent) + : q->filterAcceptsColumn(i, source_parent)) { + source_items.append(i); + } + } + + // Sort and insert the items + if (orient == Qt::Vertical) // Only sort rows + sort_source_rows(source_items, source_parent); + insert_source_items(source_to_proxy, proxy_to_source, source_items, source_parent, orient); +} + +/*! + \internal + + Handles source model items removal + (columnsAboutToBeRemoved(), rowsAboutToBeRemoved()). +*/ +void QSortFilterProxyModelPrivate::source_items_about_to_be_removed( + const QModelIndex &source_parent, int start, int end, Qt::Orientation orient) +{ + if ((start < 0) || (end < 0)) + return; + IndexMap::const_iterator it = source_index_mapping.constFind(source_parent); + if (it == source_index_mapping.constEnd()) { + // Don't care, since we don't have mapping for this index + return; + } + + Mapping *m = it.value(); + QVector<int> &source_to_proxy = (orient == Qt::Vertical) ? m->proxy_rows : m->proxy_columns; + QVector<int> &proxy_to_source = (orient == Qt::Vertical) ? m->source_rows : m->source_columns; + + // figure out which items to remove + QVector<int> source_items_to_remove; + int proxy_count = proxy_to_source.size(); + for (int proxy_item = 0; proxy_item < proxy_count; ++proxy_item) { + int source_item = proxy_to_source.at(proxy_item); + if ((source_item >= start) && (source_item <= end)) + source_items_to_remove.append(source_item); + } + + remove_source_items(source_to_proxy, proxy_to_source, source_items_to_remove, + source_parent, orient); +} + +/*! + \internal + + Handles source model items removal (columnsRemoved(), rowsRemoved()). +*/ +void QSortFilterProxyModelPrivate::source_items_removed( + const QModelIndex &source_parent, int start, int end, Qt::Orientation orient) +{ + if ((start < 0) || (end < 0)) + return; + IndexMap::const_iterator it = source_index_mapping.constFind(source_parent); + if (it == source_index_mapping.constEnd()) { + // Don't care, since we don't have mapping for this index + return; + } + + Mapping *m = it.value(); + QVector<int> &source_to_proxy = (orient == Qt::Vertical) ? m->proxy_rows : m->proxy_columns; + QVector<int> &proxy_to_source = (orient == Qt::Vertical) ? m->source_rows : m->source_columns; + + if (end >= source_to_proxy.size()) + end = source_to_proxy.size() - 1; + + // Shrink the source-to-proxy mapping to reflect the new item count + int delta_item_count = end - start + 1; + source_to_proxy.remove(start, delta_item_count); + + int proxy_count = proxy_to_source.size(); + if (proxy_count > source_to_proxy.size()) { + // mapping is in an inconsistent state -- redo the whole mapping + qWarning("QSortFilterProxyModel: inconsistent changes reported by source model"); + remove_from_mapping(source_parent); + Q_Q(QSortFilterProxyModel); + q->reset(); + return; + } + + // Adjust "stale" indexes in proxy-to-source mapping + for (int proxy_item = 0; proxy_item < proxy_count; ++proxy_item) { + int source_item = proxy_to_source.at(proxy_item); + if (source_item >= start) { + Q_ASSERT(source_item - delta_item_count >= 0); + proxy_to_source.replace(proxy_item, source_item - delta_item_count); + } + } + build_source_to_proxy_mapping(proxy_to_source, source_to_proxy); + + updateChildrenMapping(source_parent, m, orient, start, end, delta_item_count, true); + +} + + +/*! + \internal + updates the mapping of the children when inserting or removing items +*/ +void QSortFilterProxyModelPrivate::updateChildrenMapping(const QModelIndex &source_parent, Mapping *parent_mapping, + Qt::Orientation orient, int start, int end, int delta_item_count, bool remove) +{ + // see if any mapped children should be (re)moved + QVector<QPair<QModelIndex, Mapping*> > moved_source_index_mappings; + QVector<QModelIndex>::iterator it2 = parent_mapping->mapped_children.begin(); + for ( ; it2 != parent_mapping->mapped_children.end();) { + const QModelIndex source_child_index = *it2; + const int pos = (orient == Qt::Vertical) + ? source_child_index.row() + : source_child_index.column(); + if (pos < start) { + // not affected + ++it2; + } else if (remove && pos <= end) { + // in the removed interval + it2 = parent_mapping->mapped_children.erase(it2); + remove_from_mapping(source_child_index); + } else { + // below the removed items -- recompute the index + QModelIndex new_index; + const int newpos = remove ? pos - delta_item_count : pos + delta_item_count; + if (orient == Qt::Vertical) { + new_index = model->index(newpos, + source_child_index.column(), + source_parent); + } else { + new_index = model->index(source_child_index.row(), + newpos, + source_parent); + } + *it2 = new_index; + ++it2; + + // update mapping + Mapping *cm = source_index_mapping.take(source_child_index); + Q_ASSERT(cm); + // we do not reinsert right away, because the new index might be identical with another, old index + moved_source_index_mappings.append(QPair<QModelIndex, Mapping*>(new_index, cm)); + } + } + + // reinsert moved, mapped indexes + QVector<QPair<QModelIndex, Mapping*> >::iterator it = moved_source_index_mappings.begin(); + for (; it != moved_source_index_mappings.end(); ++it) { +#ifdef QT_STRICT_ITERATORS + source_index_mapping.insert((*it).first, (*it).second); + (*it).second->map_iter = source_index_mapping.constFind((*it).first); +#else + (*it).second->map_iter = source_index_mapping.insert((*it).first, (*it).second); +#endif + } +} + +/*! + \internal +*/ +void QSortFilterProxyModelPrivate::proxy_item_range( + const QVector<int> &source_to_proxy, const QVector<int> &source_items, + int &proxy_low, int &proxy_high) const +{ + proxy_low = INT_MAX; + proxy_high = INT_MIN; + foreach (int source_item, source_items) { + int proxy_item = source_to_proxy.at(source_item); + Q_ASSERT(proxy_item != -1); + if (proxy_item < proxy_low) + proxy_low = proxy_item; + if (proxy_item > proxy_high) + proxy_high = proxy_item; + } +} + +/*! + \internal +*/ +void QSortFilterProxyModelPrivate::build_source_to_proxy_mapping( + const QVector<int> &proxy_to_source, QVector<int> &source_to_proxy) const +{ + source_to_proxy.fill(-1); + int proxy_count = proxy_to_source.size(); + for (int i = 0; i < proxy_count; ++i) + source_to_proxy[proxy_to_source.at(i)] = i; +} + +/*! + \internal + + Maps the persistent proxy indexes to source indexes and + returns the list of source indexes. +*/ +QModelIndexPairList QSortFilterProxyModelPrivate::store_persistent_indexes() +{ + Q_Q(QSortFilterProxyModel); + QModelIndexPairList source_indexes; + foreach (QPersistentModelIndexData *data, persistent.indexes) { + QModelIndex proxy_index = data->index; + QModelIndex source_index = q->mapToSource(proxy_index); + source_indexes.append(qMakePair(proxy_index, QPersistentModelIndex(source_index))); + } + return source_indexes; +} + +/*! + \internal + + Maps \a source_indexes to proxy indexes and stores those + as persistent indexes. +*/ +void QSortFilterProxyModelPrivate::update_persistent_indexes( + const QModelIndexPairList &source_indexes) +{ + Q_Q(QSortFilterProxyModel); + QModelIndexList from, to; + for (int i = 0; i < source_indexes.count(); ++i) { + QModelIndex source_index = source_indexes.at(i).second; + QModelIndex old_proxy_index = source_indexes.at(i).first; + create_mapping(source_index.parent()); + QModelIndex proxy_index = q->mapFromSource(source_index); + from << old_proxy_index; + to << proxy_index; + } + q->changePersistentIndexList(from, to); +} + +/*! + \internal + + Updates the proxy model (adds/removes rows) based on the + new filter. +*/ +void QSortFilterProxyModelPrivate::filter_changed() +{ + QMap<QModelIndex, Mapping *>::const_iterator it; + for (it = source_index_mapping.constBegin(); it != source_index_mapping.constEnd(); ++it) { + QModelIndex source_parent = it.key(); + Mapping *m = it.value(); + handle_filter_changed(m->proxy_rows, m->source_rows, source_parent, Qt::Vertical); + handle_filter_changed(m->proxy_columns, m->source_columns, source_parent, Qt::Horizontal); + } +} + +/*! + \internal +*/ +void QSortFilterProxyModelPrivate::handle_filter_changed( + QVector<int> &source_to_proxy, QVector<int> &proxy_to_source, + const QModelIndex &source_parent, Qt::Orientation orient) +{ + Q_Q(QSortFilterProxyModel); + // Figure out which mapped items to remove + QVector<int> source_items_remove; + foreach (int source_item, proxy_to_source) { + if ((orient == Qt::Vertical) + ? !q->filterAcceptsRow(source_item, source_parent) + : !q->filterAcceptsColumn(source_item, source_parent)) { + // This source item does not satisfy the filter, so it must be removed + source_items_remove.append(source_item); + } + } + // Figure out which non-mapped items to insert + QVector<int> source_items_insert; + int source_count = source_to_proxy.size(); + for (int source_item = 0; source_item < source_count; ++source_item) { + if (source_to_proxy.at(source_item) == -1) { + if ((orient == Qt::Vertical) + ? q->filterAcceptsRow(source_item, source_parent) + : q->filterAcceptsColumn(source_item, source_parent)) { + // This source item satisfies the filter, so it must be added + source_items_insert.append(source_item); + } + } + } + if (!source_items_remove.isEmpty() || !source_items_insert.isEmpty()) { + // Do item removal and insertion + remove_source_items(source_to_proxy, proxy_to_source, + source_items_remove, source_parent, orient); + if (orient == Qt::Vertical) + sort_source_rows(source_items_insert, source_parent); + insert_source_items(source_to_proxy, proxy_to_source, + source_items_insert, source_parent, orient); + } +} + +void QSortFilterProxyModelPrivate::_q_sourceDataChanged(const QModelIndex &source_top_left, + const QModelIndex &source_bottom_right) +{ + Q_Q(QSortFilterProxyModel); + if (!source_top_left.isValid() || !source_bottom_right.isValid()) + return; + QModelIndex source_parent = source_top_left.parent(); + IndexMap::const_iterator it = create_mapping(source_parent); + if (it == source_index_mapping.constEnd()) { + // Don't care, since we don't have mapping for this index + return; + } + Mapping *m = it.value(); + + // Figure out how the source changes affect us + QVector<int> source_rows_remove; + QVector<int> source_rows_insert; + QVector<int> source_rows_change; + QVector<int> source_rows_resort; + int end = qMin(source_bottom_right.row(), m->proxy_rows.count() - 1); + for (int source_row = source_top_left.row(); source_row <= end; ++source_row) { + if (dynamic_sortfilter) { + if (m->proxy_rows.at(source_row) != -1) { + if (!q->filterAcceptsRow(source_row, source_parent)) { + // This source row no longer satisfies the filter, so it must be removed + source_rows_remove.append(source_row); + } else if (source_sort_column >= source_top_left.column() && source_sort_column <= source_bottom_right.column()) { + // This source row has changed in a way that may affect sorted order + source_rows_resort.append(source_row); + } else { + // This row has simply changed, without affecting filtering nor sorting + source_rows_change.append(source_row); + } + } else { + if (q->filterAcceptsRow(source_row, source_parent)) { + // This source row now satisfies the filter, so it must be added + source_rows_insert.append(source_row); + } + } + } else { + if (m->proxy_rows.at(source_row) != -1) + source_rows_change.append(source_row); + } + } + + if (!source_rows_remove.isEmpty()) + remove_source_items(m->proxy_rows, m->source_rows, + source_rows_remove, source_parent, Qt::Vertical); + + if (!source_rows_resort.isEmpty()) { + // Re-sort the rows + emit q->layoutAboutToBeChanged(); + QModelIndexPairList source_indexes = store_persistent_indexes(); + remove_source_items(m->proxy_rows, m->source_rows, source_rows_resort, + source_parent, Qt::Vertical, false); + sort_source_rows(source_rows_resort, source_parent); + insert_source_items(m->proxy_rows, m->source_rows, source_rows_resort, + source_parent, Qt::Vertical, false); + update_persistent_indexes(source_indexes); + emit q->layoutChanged(); + // Make sure we also emit dataChanged for the rows + source_rows_change += source_rows_resort; + } + + if (!source_rows_change.isEmpty()) { + // Find the proxy row range + int proxy_start_row; + int proxy_end_row; + proxy_item_range(m->proxy_rows, source_rows_change, + proxy_start_row, proxy_end_row); + // ### Find the proxy column range also + if (proxy_end_row >= 0) { + // the row was accepted, but some columns might still be filtered out + int source_left_column = source_top_left.column(); + while (source_left_column < source_bottom_right.column() + && m->proxy_columns.at(source_left_column) == -1) + ++source_left_column; + const QModelIndex proxy_top_left = create_index( + proxy_start_row, m->proxy_columns.at(source_left_column), it); + int source_right_column = source_bottom_right.column(); + while (source_right_column > source_top_left.column() + && m->proxy_columns.at(source_right_column) == -1) + --source_right_column; + const QModelIndex proxy_bottom_right = create_index( + proxy_end_row, m->proxy_columns.at(source_right_column), it); + emit q->dataChanged(proxy_top_left, proxy_bottom_right); + } + } + + if (!source_rows_insert.isEmpty()) { + sort_source_rows(source_rows_insert, source_parent); + insert_source_items(m->proxy_rows, m->source_rows, + source_rows_insert, source_parent, Qt::Vertical); + } +} + +void QSortFilterProxyModelPrivate::_q_sourceHeaderDataChanged(Qt::Orientation orientation, + int start, int end) +{ + Q_Q(QSortFilterProxyModel); + Mapping *m = create_mapping(QModelIndex()).value(); + int proxy_start = (orientation == Qt::Vertical + ? m->proxy_rows.at(start) + : m->proxy_columns.at(start)); + int proxy_end = (orientation == Qt::Vertical + ? m->proxy_rows.at(end) + : m->proxy_columns.at(end)); + emit q->headerDataChanged(orientation, proxy_start, proxy_end); +} + +void QSortFilterProxyModelPrivate::_q_sourceReset() +{ + Q_Q(QSortFilterProxyModel); + // All internal structures are deleted in clear() + q->reset(); + update_source_sort_column(); +} + +void QSortFilterProxyModelPrivate::_q_sourceLayoutAboutToBeChanged() +{ + Q_Q(QSortFilterProxyModel); + saved_persistent_indexes.clear(); + if (persistent.indexes.isEmpty()) + return; + emit q->layoutAboutToBeChanged(); + saved_persistent_indexes = store_persistent_indexes(); +} + +void QSortFilterProxyModelPrivate::_q_sourceLayoutChanged() +{ + Q_Q(QSortFilterProxyModel); + if (saved_persistent_indexes.isEmpty()) { + q->invalidate(); + return; + } + + qDeleteAll(source_index_mapping); + source_index_mapping.clear(); + + update_persistent_indexes(saved_persistent_indexes); + saved_persistent_indexes.clear(); + + update_source_sort_column(); + + emit q->layoutChanged(); +} + +void QSortFilterProxyModelPrivate::_q_sourceRowsAboutToBeInserted( + const QModelIndex &source_parent, int start, int end) +{ + Q_UNUSED(start); + Q_UNUSED(end); + //Force the creation of a mapping now, even if its empty. + //We need it because the proxy can be acessed at the moment it emits rowsAboutToBeInserted in insert_source_items + create_mapping(source_parent); +} + +void QSortFilterProxyModelPrivate::_q_sourceRowsInserted( + const QModelIndex &source_parent, int start, int end) +{ + source_items_inserted(source_parent, start, end, Qt::Vertical); + if (update_source_sort_column()) //previous call to update_source_sort_column may fail if the model has no column. + sort(); // now it should succeed so we need to make sure to sort again +} + +void QSortFilterProxyModelPrivate::_q_sourceRowsAboutToBeRemoved( + const QModelIndex &source_parent, int start, int end) +{ + source_items_about_to_be_removed(source_parent, start, end, + Qt::Vertical); +} + +void QSortFilterProxyModelPrivate::_q_sourceRowsRemoved( + const QModelIndex &source_parent, int start, int end) +{ + source_items_removed(source_parent, start, end, Qt::Vertical); +} + +void QSortFilterProxyModelPrivate::_q_sourceColumnsAboutToBeInserted( + const QModelIndex &source_parent, int start, int end) +{ + Q_UNUSED(start); + Q_UNUSED(end); + //Force the creation of a mapping now, even if its empty. + //We need it because the proxy can be acessed at the moment it emits columnsAboutToBeInserted in insert_source_items + create_mapping(source_parent); +} + +void QSortFilterProxyModelPrivate::_q_sourceColumnsInserted( + const QModelIndex &source_parent, int start, int end) +{ + Q_Q(const QSortFilterProxyModel); + source_items_inserted(source_parent, start, end, Qt::Horizontal); + + if (source_parent.isValid()) + return; //we sort according to the root column only + if (source_sort_column == -1) { + //we update the source_sort_column depending on the prox_sort_column + if (update_source_sort_column()) + sort(); + } else { + if (start <= source_sort_column) + source_sort_column += end - start + 1; + + proxy_sort_column = q->mapFromSource(model->index(0,source_sort_column, source_parent)).column(); + } +} + +void QSortFilterProxyModelPrivate::_q_sourceColumnsAboutToBeRemoved( + const QModelIndex &source_parent, int start, int end) +{ + source_items_about_to_be_removed(source_parent, start, end, + Qt::Horizontal); +} + +void QSortFilterProxyModelPrivate::_q_sourceColumnsRemoved( + const QModelIndex &source_parent, int start, int end) +{ + Q_Q(const QSortFilterProxyModel); + source_items_removed(source_parent, start, end, Qt::Horizontal); + + if (source_parent.isValid()) + return; //we sort according to the root column only + if (start <= source_sort_column) { + if (end < source_sort_column) + source_sort_column -= end - start + 1; + else + source_sort_column = -1; + } + + proxy_sort_column = q->mapFromSource(model->index(0,source_sort_column, source_parent)).column(); +} + +/*! + \since 4.1 + \class QSortFilterProxyModel + \brief The QSortFilterProxyModel class provides support for sorting and filtering data passed + between another model and a view. + + \ingroup model-view + + QSortFilterProxyModel can be used for sorting items, filtering + out items, or both. The model transforms the structure of a + source model by mapping the model indexes it supplies to new + indexes, corresponding to different locations, for views to use. + This approach allows a given source model to be restructured as + far as views are concerned without requiring any transformations + on the underlying data, and without duplicating the data in + memory. + + Let's assume that we want to sort and filter the items provided + by a custom model. The code to set up the model and the view, \e + without sorting and filtering, would look like this: + + \snippet doc/src/snippets/qsortfilterproxymodel-details/main.cpp 1 + + To add sorting and filtering support to \c MyItemModel, we need + to create a QSortFilterProxyModel, call setSourceModel() with the + \c MyItemModel as argument, and install the QSortFilterProxyModel + on the view: + + \snippet doc/src/snippets/qsortfilterproxymodel-details/main.cpp 0 + \snippet doc/src/snippets/qsortfilterproxymodel-details/main.cpp 2 + + At this point, neither sorting nor filtering is enabled; the + original data is displayed in the view. Any changes made through + the QSortFilterProxyModel are applied to the original model. + + The QSortFilterProxyModel acts as a wrapper for the original + model. If you need to convert source \l{QModelIndex}es to + sorted/filtered model indexes or vice versa, use mapToSource(), + mapFromSource(), mapSelectionToSource(), and + mapSelectionFromSource(). + + \note By default, the model does not dynamically re-sort and re-filter + data whenever the original model changes. This behavior can be + changed by setting the \l{QSortFilterProxyModel::dynamicSortFilter} + {dynamicSortFilter} property. + + The \l{itemviews/basicsortfiltermodel}{Basic Sort/Filter Model} + and \l{itemviews/customsortfiltermodel}{Custom Sort/Filter Model} + examples illustrate how to use QSortFilterProxyModel to perform + basic sorting and filtering and how to subclass it to implement + custom behavior. + + \section1 Sorting + + QTableView and QTreeView have a + \l{QTreeView::sortingEnabled}{sortingEnabled} property that + controls whether the user can sort the view by clicking the + view's horizontal header. For example: + + \snippet doc/src/snippets/qsortfilterproxymodel-details/main.cpp 3 + + When this feature is on (the default is off), clicking on a + header section sorts the items according to that column. By + clicking repeatedly, the user can alternate between ascending and + descending order. + + \image qsortfilterproxymodel-sorting.png A sorted QTreeView + + Behind the scene, the view calls the sort() virtual function on + the model to reorder the data in the model. To make your data + sortable, you can either implement sort() in your model, or you + use a QSortFilterProxyModel to wrap your model -- + QSortFilterProxyModel provides a generic sort() reimplementation + that operates on the sortRole() (Qt::DisplayRole by default) of + the items and that understands several data types, including \c + int, QString, and QDateTime. For hierarchical models, sorting is + applied recursively to all child items. String comparisons are + case sensitive by default; this can be changed by setting the + \l{QSortFilterProxyModel::}{sortCaseSensitivity} property. + + Custom sorting behavior is achieved by subclassing + QSortFilterProxyModel and reimplementing lessThan(), which is + used to compare items. For example: + + \snippet examples/itemviews/customsortfiltermodel/mysortfilterproxymodel.cpp 5 + + (This code snippet comes from the + \l{itemviews/customsortfiltermodel}{Custom Sort/Filter Model} + example.) + + An alternative approach to sorting is to disable sorting on the + view and to impose a certain order to the user. This is done by + explicitly calling sort() with the desired column and order as + arguments on the QSortFilterProxyModel (or on the original model + if it implements sort()). For example: + + \snippet doc/src/snippets/qsortfilterproxymodel-details/main.cpp 4 + + QSortFilterProxyModel can be sorted by column -1, in which case it + returns to the sort order of the underlying source model. + + \section1 Filtering + + In addition to sorting, QSortFilterProxyModel can be used to hide + items that don't match a certain filter. The filter is specified + using a QRegExp object and is applied to the filterRole() + (Qt::DisplayRole by default) of each item, for a given column. + The QRegExp object can be used to match a regular expression, a + wildcard pattern, or a fixed string. For example: + + \snippet doc/src/snippets/qsortfilterproxymodel-details/main.cpp 5 + + For hierarchical models, the filter is applied recursively to all + children. If a parent item doesn't match the filter, none of its + children will be shown. + + A common use case is to let the user specify the filter regexp, + wildcard pattern, or fixed string in a QLineEdit and to connect + the \l{QLineEdit::textChanged()}{textChanged()} signal to + setFilterRegExp(), setFilterWildcard(), or setFilterFixedString() + to reapply the filter. + + Custom filtering behavior can be achieved by reimplementing the + filterAcceptsRow() and filterAcceptsColumn() functions. For + example, the following implementation ignores the + \l{QSortFilterProxyModel::filterKeyColumn}{filterKeyColumn} + property and performs filtering on columns 0, 1, and 2: + + \snippet examples/itemviews/customsortfiltermodel/mysortfilterproxymodel.cpp 3 + + (This code snippet comes from the + \l{itemviews/customsortfiltermodel}{Custom Sort/Filter Model} + example.) + + If you are working with large amounts of filtering and have to invoke + invalidateFilter() repeatedly, using reset() may be more efficient, + depending on the implementation of your model. However, note that reset() + returns the proxy model to its original state, losing selection + information, and will cause the proxy model to be repopulated. + + \section1 Subclassing + + \bold{Note:} Some general guidelines for subclassing models are + available in the \l{Model Subclassing Reference}. + + Since QAbstractProxyModel and its subclasses are derived from + QAbstractItemModel, much of the same advice about subclassing normal + models also applies to proxy models. In addition, it is worth noting + that many of the default implementations of functions in this class + are written so that they call the equivalent functions in the relevant + source model. This simple proxying mechanism may need to be overridden + for source models with more complex behavior; for example, if the + source model provides a custom hasChildren() implementation, you + should also provide one in the proxy model. + + \sa QAbstractProxyModel, QAbstractItemModel, {Model/View Programming}, + {Basic Sort/Filter Model Example}, {Custom Sort/Filter Model Example} +*/ + +/*! + Constructs a sorting filter model with the given \a parent. +*/ + +QSortFilterProxyModel::QSortFilterProxyModel(QObject *parent) + : QAbstractProxyModel(*new QSortFilterProxyModelPrivate, parent) +{ + Q_D(QSortFilterProxyModel); + d->proxy_sort_column = d->source_sort_column = -1; + d->sort_order = Qt::AscendingOrder; + d->sort_casesensitivity = Qt::CaseSensitive; + d->sort_role = Qt::DisplayRole; + d->sort_localeaware = false; + d->filter_column = 0; + d->filter_role = Qt::DisplayRole; + d->dynamic_sortfilter = false; + connect(this, SIGNAL(modelReset()), this, SLOT(invalidate())); +} + +/*! + Destroys this sorting filter model. +*/ +QSortFilterProxyModel::~QSortFilterProxyModel() +{ + Q_D(QSortFilterProxyModel); + qDeleteAll(d->source_index_mapping); + d->source_index_mapping.clear(); +} + +/*! + \reimp +*/ +void QSortFilterProxyModel::setSourceModel(QAbstractItemModel *sourceModel) +{ + Q_D(QSortFilterProxyModel); + + disconnect(d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_sourceDataChanged(QModelIndex,QModelIndex))); + + disconnect(d->model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), + this, SLOT(_q_sourceHeaderDataChanged(Qt::Orientation,int,int))); + + disconnect(d->model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsAboutToBeInserted(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsInserted(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsAboutToBeInserted(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsInserted(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsAboutToBeRemoved(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsRemoved(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsAboutToBeRemoved(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsRemoved(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(layoutAboutToBeChanged()), + this, SLOT(_q_sourceLayoutAboutToBeChanged())); + + disconnect(d->model, SIGNAL(layoutChanged()), + this, SLOT(_q_sourceLayoutChanged())); + + disconnect(d->model, SIGNAL(modelReset()), this, SLOT(_q_sourceReset())); + + QAbstractProxyModel::setSourceModel(sourceModel); + + connect(d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_sourceDataChanged(QModelIndex,QModelIndex))); + + connect(d->model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), + this, SLOT(_q_sourceHeaderDataChanged(Qt::Orientation,int,int))); + + connect(d->model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsAboutToBeInserted(QModelIndex,int,int))); + + connect(d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsInserted(QModelIndex,int,int))); + + connect(d->model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsAboutToBeInserted(QModelIndex,int,int))); + + connect(d->model, SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsInserted(QModelIndex,int,int))); + + connect(d->model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsAboutToBeRemoved(QModelIndex,int,int))); + + connect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsRemoved(QModelIndex,int,int))); + + connect(d->model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsAboutToBeRemoved(QModelIndex,int,int))); + + connect(d->model, SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsRemoved(QModelIndex,int,int))); + + connect(d->model, SIGNAL(layoutAboutToBeChanged()), + this, SLOT(_q_sourceLayoutAboutToBeChanged())); + + connect(d->model, SIGNAL(layoutChanged()), + this, SLOT(_q_sourceLayoutChanged())); + + connect(d->model, SIGNAL(modelReset()), this, SLOT(_q_sourceReset())); + + d->clear_mapping(); + reset(); +} + +/*! + \reimp +*/ +QModelIndex QSortFilterProxyModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_D(const QSortFilterProxyModel); + if (row < 0 || column < 0) + return QModelIndex(); + + QModelIndex source_parent = mapToSource(parent); // parent is already mapped at this point + IndexMap::const_iterator it = d->create_mapping(source_parent); // but make sure that the children are mapped + if (it.value()->source_rows.count() <= row || it.value()->source_columns.count() <= column) + return QModelIndex(); + + return d->create_index(row, column, it); +} + +/*! + \reimp +*/ +QModelIndex QSortFilterProxyModel::parent(const QModelIndex &child) const +{ + Q_D(const QSortFilterProxyModel); + if (!d->indexValid(child)) + return QModelIndex(); + IndexMap::const_iterator it = d->index_to_iterator(child); + Q_ASSERT(it != d->source_index_mapping.constEnd()); + QModelIndex source_parent = it.key(); + QModelIndex proxy_parent = mapFromSource(source_parent); + return proxy_parent; +} + +/*! + \reimp +*/ +int QSortFilterProxyModel::rowCount(const QModelIndex &parent) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return 0; + IndexMap::const_iterator it = d->create_mapping(source_parent); + return it.value()->source_rows.count(); +} + +/*! + \reimp +*/ +int QSortFilterProxyModel::columnCount(const QModelIndex &parent) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return 0; + IndexMap::const_iterator it = d->create_mapping(source_parent); + return it.value()->source_columns.count(); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::hasChildren(const QModelIndex &parent) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return false; + if (!d->model->hasChildren(source_parent)) + return false; + QSortFilterProxyModelPrivate::Mapping *m = d->create_mapping(source_parent).value(); + return m->source_rows.count() != 0 && m->source_columns.count() != 0; +} + +/*! + \reimp +*/ +QVariant QSortFilterProxyModel::data(const QModelIndex &index, int role) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_index = mapToSource(index); + if (index.isValid() && !source_index.isValid()) + return QVariant(); + return d->model->data(source_index, role); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + Q_D(QSortFilterProxyModel); + QModelIndex source_index = mapToSource(index); + if (index.isValid() && !source_index.isValid()) + return false; + return d->model->setData(source_index, value, role); +} + +/*! + \reimp +*/ +QVariant QSortFilterProxyModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_D(const QSortFilterProxyModel); + IndexMap::const_iterator it = d->create_mapping(QModelIndex()); + if (it.value()->source_rows.count() * it.value()->source_columns.count() > 0) + return QAbstractProxyModel::headerData(section, orientation, role); + int source_section; + if (orientation == Qt::Vertical) { + if (section < 0 || section >= it.value()->source_rows.count()) + return QVariant(); + source_section = it.value()->source_rows.at(section); + } else { + if (section < 0 || section >= it.value()->source_columns.count()) + return QVariant(); + source_section = it.value()->source_columns.at(section); + } + return d->model->headerData(source_section, orientation, role); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::setHeaderData(int section, Qt::Orientation orientation, + const QVariant &value, int role) +{ + Q_D(QSortFilterProxyModel); + IndexMap::const_iterator it = d->create_mapping(QModelIndex()); + if (it.value()->source_rows.count() * it.value()->source_columns.count() > 0) + return QAbstractProxyModel::setHeaderData(section, orientation, value, role); + int source_section; + if (orientation == Qt::Vertical) { + if (section < 0 || section >= it.value()->source_rows.count()) + return false; + source_section = it.value()->source_rows.at(section); + } else { + if (section < 0 || section >= it.value()->source_columns.count()) + return false; + source_section = it.value()->source_columns.at(section); + } + return d->model->setHeaderData(source_section, orientation, value, role); +} + +/*! + \reimp +*/ +QMimeData *QSortFilterProxyModel::mimeData(const QModelIndexList &indexes) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndexList source_indexes; + for (int i = 0; i < indexes.count(); ++i) + source_indexes << mapToSource(indexes.at(i)); + return d->model->mimeData(source_indexes); +} + +/*! + \reimp +*/ +QStringList QSortFilterProxyModel::mimeTypes() const +{ + Q_D(const QSortFilterProxyModel); + return d->model->mimeTypes(); +} + +/*! + \reimp +*/ +Qt::DropActions QSortFilterProxyModel::supportedDropActions() const +{ + Q_D(const QSortFilterProxyModel); + return d->model->supportedDropActions(); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent) +{ + Q_D(QSortFilterProxyModel); + if ((row == -1) && (column == -1)) + return d->model->dropMimeData(data, action, -1, -1, mapToSource(parent)); + int source_destination_row = -1; + int source_destination_column = -1; + QModelIndex source_parent; + if (row == rowCount(parent)) { + source_parent = mapToSource(parent); + source_destination_row = d->model->rowCount(source_parent); + } else { + QModelIndex proxy_index = index(row, column, parent); + QModelIndex source_index = mapToSource(proxy_index); + source_destination_row = source_index.row(); + source_destination_column = source_index.column(); + source_parent = source_index.parent(); + } + return d->model->dropMimeData(data, action, source_destination_row, + source_destination_column, source_parent); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::insertRows(int row, int count, const QModelIndex &parent) +{ + Q_D(QSortFilterProxyModel); + if (row < 0 || count <= 0) + return false; + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return false; + QSortFilterProxyModelPrivate::Mapping *m = d->create_mapping(source_parent).value(); + if (row > m->source_rows.count()) + return false; + int source_row = (row >= m->source_rows.count() + ? m->source_rows.count() + : m->source_rows.at(row)); + return d->model->insertRows(source_row, count, source_parent); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::insertColumns(int column, int count, const QModelIndex &parent) +{ + Q_D(QSortFilterProxyModel); + if (column < 0|| count <= 0) + return false; + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return false; + QSortFilterProxyModelPrivate::Mapping *m = d->create_mapping(source_parent).value(); + if (column > m->source_columns.count()) + return false; + int source_column = (column >= m->source_columns.count() + ? m->source_columns.count() + : m->source_columns.at(column)); + return d->model->insertColumns(source_column, count, source_parent); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::removeRows(int row, int count, const QModelIndex &parent) +{ + Q_D(QSortFilterProxyModel); + if (row < 0 || count <= 0) + return false; + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return false; + QSortFilterProxyModelPrivate::Mapping *m = d->create_mapping(source_parent).value(); + if (row + count > m->source_rows.count()) + return false; + if ((count == 1) + || ((d->source_sort_column < 0) && (m->proxy_rows.count() == m->source_rows.count()))) { + int source_row = m->source_rows.at(row); + return d->model->removeRows(source_row, count, source_parent); + } + // remove corresponding source intervals + // ### if this proves to be slow, we can switch to single-row removal + QVector<int> rows; + for (int i = row; i < row + count; ++i) + rows.append(m->source_rows.at(i)); + qSort(rows.begin(), rows.end()); + + int pos = rows.count() - 1; + bool ok = true; + while (pos >= 0) { + const int source_end = rows.at(pos--); + int source_start = source_end; + while ((pos >= 0) && (rows.at(pos) == (source_start - 1))) { + --source_start; + --pos; + } + ok = ok && d->model->removeRows(source_start, source_end - source_start + 1, + source_parent); + } + return ok; +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::removeColumns(int column, int count, const QModelIndex &parent) +{ + Q_D(QSortFilterProxyModel); + if (column < 0 || count <= 0) + return false; + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return false; + QSortFilterProxyModelPrivate::Mapping *m = d->create_mapping(source_parent).value(); + if (column + count > m->source_columns.count()) + return false; + if ((count == 1) || (m->proxy_columns.count() == m->source_columns.count())) { + int source_column = m->source_columns.at(column); + return d->model->removeColumns(source_column, count, source_parent); + } + // remove corresponding source intervals + QVector<int> columns; + for (int i = column; i < column + count; ++i) + columns.append(m->source_columns.at(i)); + + int pos = columns.count() - 1; + bool ok = true; + while (pos >= 0) { + const int source_end = columns.at(pos--); + int source_start = source_end; + while ((pos >= 0) && (columns.at(pos) == (source_start - 1))) { + --source_start; + --pos; + } + ok = ok && d->model->removeColumns(source_start, source_end - source_start + 1, + source_parent); + } + return ok; +} + +/*! + \reimp +*/ +void QSortFilterProxyModel::fetchMore(const QModelIndex &parent) +{ + Q_D(QSortFilterProxyModel); + QModelIndex source_parent; + if (d->indexValid(parent)) + source_parent = mapToSource(parent); + d->model->fetchMore(source_parent); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::canFetchMore(const QModelIndex &parent) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_parent; + if (d->indexValid(parent)) + source_parent = mapToSource(parent); + return d->model->canFetchMore(source_parent); +} + +/*! + \reimp +*/ +Qt::ItemFlags QSortFilterProxyModel::flags(const QModelIndex &index) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_index; + if (d->indexValid(index)) + source_index = mapToSource(index); + return d->model->flags(source_index); +} + +/*! + \reimp +*/ +QModelIndex QSortFilterProxyModel::buddy(const QModelIndex &index) const +{ + Q_D(const QSortFilterProxyModel); + if (!d->indexValid(index)) + return QModelIndex(); + QModelIndex source_index = mapToSource(index); + QModelIndex source_buddy = d->model->buddy(source_index); + if (source_index == source_buddy) + return index; + return mapFromSource(source_buddy); +} + +/*! + \reimp +*/ +QModelIndexList QSortFilterProxyModel::match(const QModelIndex &start, int role, + const QVariant &value, int hits, + Qt::MatchFlags flags) const +{ + return QAbstractProxyModel::match(start, role, value, hits, flags); +} + +/*! + \reimp +*/ +QSize QSortFilterProxyModel::span(const QModelIndex &index) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_index = mapToSource(index); + if (index.isValid() && !source_index.isValid()) + return QSize(); + return d->model->span(source_index); +} + +/*! + \reimp +*/ +void QSortFilterProxyModel::sort(int column, Qt::SortOrder order) +{ + Q_D(QSortFilterProxyModel); + if (d->proxy_sort_column == column && d->sort_order == order) + return; + d->sort_order = order; + d->proxy_sort_column = column; + d->update_source_sort_column(); + d->sort(); +} + +/*! + \since 4.5 + \brief the column currently used for sorting + + This returns the most recently used sort column. +*/ +int QSortFilterProxyModel::sortColumn() const +{ + Q_D(const QSortFilterProxyModel); + return d->proxy_sort_column; +} + +/*! + \since 4.5 + \brief the order currently used for sorting + + This returns the most recently used sort order. +*/ +Qt::SortOrder QSortFilterProxyModel::sortOrder() const +{ + Q_D(const QSortFilterProxyModel); + return d->sort_order; +} + +/*! + \property QSortFilterProxyModel::filterRegExp + \brief the QRegExp used to filter the contents of the source model + + Setting this property overwrites the current + \l{QSortFilterProxyModel::filterCaseSensitivity} + {filterCaseSensitivity}. By default, the QRegExp is an empty + string matching all contents. + + \sa filterCaseSensitivity, setFilterWildcard(), setFilterFixedString() +*/ +QRegExp QSortFilterProxyModel::filterRegExp() const +{ + Q_D(const QSortFilterProxyModel); + return d->filter_regexp; +} + +void QSortFilterProxyModel::setFilterRegExp(const QRegExp ®Exp) +{ + Q_D(QSortFilterProxyModel); + d->filter_regexp = regExp; + d->filter_changed(); +} + +/*! + \property QSortFilterProxyModel::filterKeyColumn + \brief the column where the key used to filter the contents of the + source model is read from. + + The default value is 0. If the value is -1, the keys will be read + from all columns. +*/ +int QSortFilterProxyModel::filterKeyColumn() const +{ + Q_D(const QSortFilterProxyModel); + return d->filter_column; +} + +void QSortFilterProxyModel::setFilterKeyColumn(int column) +{ + Q_D(QSortFilterProxyModel); + d->filter_column = column; + d->filter_changed(); +} + +/*! + \property QSortFilterProxyModel::filterCaseSensitivity + + \brief the case sensitivity of the QRegExp pattern used to filter the + contents of the source model + + By default, the filter is case sensitive. + + \sa filterRegExp, sortCaseSensitivity +*/ +Qt::CaseSensitivity QSortFilterProxyModel::filterCaseSensitivity() const +{ + Q_D(const QSortFilterProxyModel); + return d->filter_regexp.caseSensitivity(); +} + +void QSortFilterProxyModel::setFilterCaseSensitivity(Qt::CaseSensitivity cs) +{ + Q_D(QSortFilterProxyModel); + if (cs == d->filter_regexp.caseSensitivity()) + return; + d->filter_regexp.setCaseSensitivity(cs); + d->filter_changed(); +} + +/*! + \since 4.2 + \property QSortFilterProxyModel::sortCaseSensitivity + \brief the case sensitivity setting used for comparing strings when sorting + + By default, sorting is case sensitive. + + \sa filterCaseSensitivity, lessThan() +*/ +Qt::CaseSensitivity QSortFilterProxyModel::sortCaseSensitivity() const +{ + Q_D(const QSortFilterProxyModel); + return d->sort_casesensitivity; +} + +void QSortFilterProxyModel::setSortCaseSensitivity(Qt::CaseSensitivity cs) +{ + Q_D(QSortFilterProxyModel); + if (d->sort_casesensitivity == cs) + return; + + d->sort_casesensitivity = cs; + d->sort(); +} + +/*! + \since 4.3 + \property QSortFilterProxyModel::isSortLocaleAware + \brief the local aware setting used for comparing strings when sorting + + By default, sorting is not local aware. + + \sa sortCaseSensitivity, lessThan() +*/ +bool QSortFilterProxyModel::isSortLocaleAware() const +{ + Q_D(const QSortFilterProxyModel); + return d->sort_localeaware; +} + +void QSortFilterProxyModel::setSortLocaleAware(bool on) +{ + Q_D(QSortFilterProxyModel); + if (d->sort_localeaware == on) + return; + + d->sort_localeaware = on; + d->sort(); +} + +/*! + \overload + + Sets the regular expression used to filter the contents + of the source model to \a pattern. + + \sa setFilterCaseSensitivity(), setFilterWildcard(), setFilterFixedString(), filterRegExp() +*/ +void QSortFilterProxyModel::setFilterRegExp(const QString &pattern) +{ + Q_D(QSortFilterProxyModel); + d->filter_regexp.setPatternSyntax(QRegExp::RegExp); + d->filter_regexp.setPattern(pattern); + d->filter_changed(); +} + +/*! + Sets the wildcard expression used to filter the contents + of the source model to the given \a pattern. + + \sa setFilterCaseSensitivity(), setFilterRegExp(), setFilterFixedString(), filterRegExp() +*/ +void QSortFilterProxyModel::setFilterWildcard(const QString &pattern) +{ + Q_D(QSortFilterProxyModel); + d->filter_regexp.setPatternSyntax(QRegExp::Wildcard); + d->filter_regexp.setPattern(pattern); + d->filter_changed(); +} + +/*! + Sets the fixed string used to filter the contents + of the source model to the given \a pattern. + + \sa setFilterCaseSensitivity(), setFilterRegExp(), setFilterWildcard(), filterRegExp() +*/ +void QSortFilterProxyModel::setFilterFixedString(const QString &pattern) +{ + Q_D(QSortFilterProxyModel); + d->filter_regexp.setPatternSyntax(QRegExp::FixedString); + d->filter_regexp.setPattern(pattern); + d->filter_changed(); +} + +/*! + \since 4.2 + \property QSortFilterProxyModel::dynamicSortFilter + \brief whether the proxy model is dynamically sorted and filtered + whenever the contents of the source model change + + The default value is false. +*/ +bool QSortFilterProxyModel::dynamicSortFilter() const +{ + Q_D(const QSortFilterProxyModel); + return d->dynamic_sortfilter; +} + +void QSortFilterProxyModel::setDynamicSortFilter(bool enable) +{ + Q_D(QSortFilterProxyModel); + d->dynamic_sortfilter = enable; +} + +/*! + \since 4.2 + \property QSortFilterProxyModel::sortRole + \brief the item role that is used to query the source model's data when sorting items + + The default value is Qt::DisplayRole. + + \sa lessThan() +*/ +int QSortFilterProxyModel::sortRole() const +{ + Q_D(const QSortFilterProxyModel); + return d->sort_role; +} + +void QSortFilterProxyModel::setSortRole(int role) +{ + Q_D(QSortFilterProxyModel); + if (d->sort_role == role) + return; + d->sort_role = role; + d->sort(); +} + +/*! + \since 4.2 + \property QSortFilterProxyModel::filterRole + \brief the item role that is used to query the source model's data when filtering items + + The default value is Qt::DisplayRole. + + \sa filterAcceptsRow() +*/ +int QSortFilterProxyModel::filterRole() const +{ + Q_D(const QSortFilterProxyModel); + return d->filter_role; +} + +void QSortFilterProxyModel::setFilterRole(int role) +{ + Q_D(QSortFilterProxyModel); + if (d->filter_role == role) + return; + d->filter_role = role; + d->filter_changed(); +} + +/*! + \obsolete + + This function is obsolete. Use invalidate() instead. +*/ +void QSortFilterProxyModel::clear() +{ + Q_D(QSortFilterProxyModel); + emit layoutAboutToBeChanged(); + d->clear_mapping(); + emit layoutChanged(); +} + +/*! + \since 4.3 + + Invalidates the current sorting and filtering. + + \sa invalidateFilter() +*/ +void QSortFilterProxyModel::invalidate() +{ + Q_D(QSortFilterProxyModel); + emit layoutAboutToBeChanged(); + d->clear_mapping(); + emit layoutChanged(); +} + +/*! + \obsolete + + This function is obsolete. Use invalidateFilter() instead. +*/ +void QSortFilterProxyModel::filterChanged() +{ + Q_D(QSortFilterProxyModel); + d->filter_changed(); +} + +/*! + \since 4.3 + + Invalidates the current filtering. + + This function should be called if you are implementing custom filtering + (e.g. filterAcceptsRow()), and your filter parameters have changed. + + \sa invalidate() +*/ +void QSortFilterProxyModel::invalidateFilter() +{ + Q_D(QSortFilterProxyModel); + d->filter_changed(); +} + +/*! + Returns true if the value of the item referred to by the given + index \a left is less than the value of the item referred to by + the given index \a right, otherwise returns false. + + This function is used as the < operator when sorting, and handles + the following QVariant types: + + \list + \o QVariant::Int + \o QVariant::UInt + \o QVariant::LongLong + \o QVariant::ULongLong + \o QVariant::Double + \o QVariant::Char + \o QVariant::Date + \o QVariant::Time + \o QVariant::DateTime + \o QVariant::String + \endlist + + Any other type will be converted to a QString using + QVariant::toString(). + + Comparison of \l{QString}s is case sensitive by default; this can + be changed using the \l {QSortFilterProxyModel::sortCaseSensitivity} + {sortCaseSensitivity} property. + + By default, the Qt::DisplayRole associated with the + \l{QModelIndex}es is used for comparisons. This can be changed by + setting the \l {QSortFilterProxyModel::sortRole} {sortRole} property. + + \note The indices passed in correspond to the source model. + + \sa sortRole, sortCaseSensitivity, dynamicSortFilter +*/ +bool QSortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + Q_D(const QSortFilterProxyModel); + QVariant l = (left.model() ? left.model()->data(left, d->sort_role) : QVariant()); + QVariant r = (right.model() ? right.model()->data(right, d->sort_role) : QVariant()); + switch (l.type()) { + case QVariant::Invalid: + return (r.type() == QVariant::Invalid); + case QVariant::Int: + return l.toInt() < r.toInt(); + case QVariant::UInt: + return l.toUInt() < r.toUInt(); + case QVariant::LongLong: + return l.toLongLong() < r.toLongLong(); + case QVariant::ULongLong: + return l.toULongLong() < r.toULongLong(); + case QVariant::Double: + return l.toDouble() < r.toDouble(); + case QVariant::Char: + return l.toChar() < r.toChar(); + case QVariant::Date: + return l.toDate() < r.toDate(); + case QVariant::Time: + return l.toTime() < r.toTime(); + case QVariant::DateTime: + return l.toDateTime() < r.toDateTime(); + case QVariant::String: + default: + if (d->sort_localeaware) + return l.toString().localeAwareCompare(r.toString()) < 0; + else + return l.toString().compare(r.toString(), d->sort_casesensitivity) < 0; + } + return false; +} + +/*! + Returns true if the item in the row indicated by the given \a source_row + and \a source_parent should be included in the model; otherwise returns + false. + + The default implementation returns true if the value held by the relevant item + matches the filter string, wildcard string or regular expression. + + \note By default, the Qt::DisplayRole is used to determine if the row + should be accepted or not. This can be changed by setting the + \l{QSortFilterProxyModel::filterRole}{filterRole} property. + + \sa filterAcceptsColumn(), setFilterFixedString(), setFilterRegExp(), setFilterWildcard() +*/ +bool QSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + Q_D(const QSortFilterProxyModel); + if (d->filter_regexp.isEmpty()) + return true; + if (d->filter_column == -1) { + int column_count = d->model->columnCount(source_parent); + for (int column = 0; column < column_count; ++column) { + QModelIndex source_index = d->model->index(source_row, column, source_parent); + QString key = d->model->data(source_index, d->filter_role).toString(); + if (key.contains(d->filter_regexp)) + return true; + } + return false; + } + QModelIndex source_index = d->model->index(source_row, d->filter_column, source_parent); + if (!source_index.isValid()) // the column may not exist + return true; + QString key = d->model->data(source_index, d->filter_role).toString(); + return key.contains(d->filter_regexp); +} + +/*! + Returns true if the item in the column indicated by the given \a source_column + and \a source_parent should be included in the model; otherwise returns false. + + The default implementation returns true if the value held by the relevant item + matches the filter string, wildcard string or regular expression. + + \note By default, the Qt::DisplayRole is used to determine if the row + should be accepted or not. This can be changed by setting the \l + filterRole property. + + \sa filterAcceptsRow(), setFilterFixedString(), setFilterRegExp(), setFilterWildcard() +*/ +bool QSortFilterProxyModel::filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const +{ + Q_UNUSED(source_column); + Q_UNUSED(source_parent); + return true; +} + +/*! + Returns the source model index corresponding to the given \a + proxyIndex from the sorting filter model. + + \sa mapFromSource() +*/ +QModelIndex QSortFilterProxyModel::mapToSource(const QModelIndex &proxyIndex) const +{ + Q_D(const QSortFilterProxyModel); + return d->proxy_to_source(proxyIndex); +} + +/*! + Returns the model index in the QSortFilterProxyModel given the \a + sourceIndex from the source model. + + \sa mapToSource() +*/ +QModelIndex QSortFilterProxyModel::mapFromSource(const QModelIndex &sourceIndex) const +{ + Q_D(const QSortFilterProxyModel); + return d->source_to_proxy(sourceIndex); +} + +/*! + \reimp +*/ +QItemSelection QSortFilterProxyModel::mapSelectionToSource(const QItemSelection &proxySelection) const +{ + return QAbstractProxyModel::mapSelectionToSource(proxySelection); +} + +/*! + \reimp +*/ +QItemSelection QSortFilterProxyModel::mapSelectionFromSource(const QItemSelection &sourceSelection) const +{ + return QAbstractProxyModel::mapSelectionFromSource(sourceSelection); +} + +/*! + \fn QObject *QSortFilterProxyModel::parent() const + \internal +*/ + +QT_END_NAMESPACE + +#include "moc_qsortfilterproxymodel.cpp" + +#endif // QT_NO_SORTFILTERPROXYMODEL diff --git a/src/gui/itemviews/qsortfilterproxymodel.h b/src/gui/itemviews/qsortfilterproxymodel.h new file mode 100644 index 0000000..f537adb --- /dev/null +++ b/src/gui/itemviews/qsortfilterproxymodel.h @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSORTFILTERPROXYMODEL_H +#define QSORTFILTERPROXYMODEL_H + +#include <QtGui/qabstractproxymodel.h> + +#ifndef QT_NO_SORTFILTERPROXYMODEL + +#include <QtCore/qregexp.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QSortFilterProxyModelPrivate; +class QSortFilterProxyModelLessThan; +class QSortFilterProxyModelGreaterThan; + +class Q_GUI_EXPORT QSortFilterProxyModel : public QAbstractProxyModel +{ + friend class QSortFilterProxyModelLessThan; + friend class QSortFilterProxyModelGreaterThan; + + Q_OBJECT + Q_PROPERTY(QRegExp filterRegExp READ filterRegExp WRITE setFilterRegExp) + Q_PROPERTY(int filterKeyColumn READ filterKeyColumn WRITE setFilterKeyColumn) + Q_PROPERTY(bool dynamicSortFilter READ dynamicSortFilter WRITE setDynamicSortFilter) + Q_PROPERTY(Qt::CaseSensitivity filterCaseSensitivity READ filterCaseSensitivity WRITE setFilterCaseSensitivity) + Q_PROPERTY(Qt::CaseSensitivity sortCaseSensitivity READ sortCaseSensitivity WRITE setSortCaseSensitivity) + Q_PROPERTY(bool isSortLocaleAware READ isSortLocaleAware WRITE setSortLocaleAware) + Q_PROPERTY(int sortRole READ sortRole WRITE setSortRole) + Q_PROPERTY(int filterRole READ filterRole WRITE setFilterRole) + +public: + QSortFilterProxyModel(QObject *parent = 0); + ~QSortFilterProxyModel(); + + void setSourceModel(QAbstractItemModel *sourceModel); + + QModelIndex mapToSource(const QModelIndex &proxyIndex) const; + QModelIndex mapFromSource(const QModelIndex &sourceIndex) const; + + QItemSelection mapSelectionToSource(const QItemSelection &proxySelection) const; + QItemSelection mapSelectionFromSource(const QItemSelection &sourceSelection) const; + + QRegExp filterRegExp() const; + void setFilterRegExp(const QRegExp ®Exp); + + int filterKeyColumn() const; + void setFilterKeyColumn(int column); + + Qt::CaseSensitivity filterCaseSensitivity() const; + void setFilterCaseSensitivity(Qt::CaseSensitivity cs); + + Qt::CaseSensitivity sortCaseSensitivity() const; + void setSortCaseSensitivity(Qt::CaseSensitivity cs); + + bool isSortLocaleAware() const; + void setSortLocaleAware(bool on); + + int sortColumn() const; + Qt::SortOrder sortOrder() const; + + bool dynamicSortFilter() const; + void setDynamicSortFilter(bool enable); + + int sortRole() const; + void setSortRole(int role); + + int filterRole() const; + void setFilterRole(int role); + +public Q_SLOTS: + void setFilterRegExp(const QString &pattern); + void setFilterWildcard(const QString &pattern); + void setFilterFixedString(const QString &pattern); + void clear(); + void invalidate(); + +protected: + virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; + virtual bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const; + virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const; + + void filterChanged(); + void invalidateFilter(); + +public: +#ifdef Q_NO_USING_KEYWORD + inline QObject *parent() const { return QObject::parent(); } +#else + using QObject::parent; +#endif + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &child) const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + bool hasChildren(const QModelIndex &parent = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + bool setHeaderData(int section, Qt::Orientation orientation, + const QVariant &value, int role = Qt::EditRole); + + QMimeData *mimeData(const QModelIndexList &indexes) const; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent); + + bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()); + bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex()); + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex()); + + void fetchMore(const QModelIndex &parent); + bool canFetchMore(const QModelIndex &parent) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + + QModelIndex buddy(const QModelIndex &index) const; + QModelIndexList match(const QModelIndex &start, int role, + const QVariant &value, int hits = 1, + Qt::MatchFlags flags = + Qt::MatchFlags(Qt::MatchStartsWith|Qt::MatchWrap)) const; + QSize span(const QModelIndex &index) const; + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + + QStringList mimeTypes() const; + Qt::DropActions supportedDropActions() const; +private: + Q_DECLARE_PRIVATE(QSortFilterProxyModel) + Q_DISABLE_COPY(QSortFilterProxyModel) + + Q_PRIVATE_SLOT(d_func(), void _q_sourceDataChanged(const QModelIndex &source_top_left, const QModelIndex &source_bottom_right)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceHeaderDataChanged(Qt::Orientation orientation, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceReset()) + Q_PRIVATE_SLOT(d_func(), void _q_sourceLayoutAboutToBeChanged()) + Q_PRIVATE_SLOT(d_func(), void _q_sourceLayoutChanged()) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsInserted(const QModelIndex &source_parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsRemoved(const QModelIndex &source_parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsAboutToBeInserted(const QModelIndex &source_parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsInserted(const QModelIndex &source_parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsRemoved(const QModelIndex &source_parent, int start, int end)) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_SORTFILTERPROXYMODEL + +#endif // QSORTFILTERPROXYMODEL_H diff --git a/src/gui/itemviews/qstandarditemmodel.cpp b/src/gui/itemviews/qstandarditemmodel.cpp new file mode 100644 index 0000000..10aac9a --- /dev/null +++ b/src/gui/itemviews/qstandarditemmodel.cpp @@ -0,0 +1,3108 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qstandarditemmodel.h" + +#ifndef QT_NO_STANDARDITEMMODEL + +#include <QtCore/qdatetime.h> +#include <QtCore/qlist.h> +#include <QtCore/qmap.h> +#include <QtCore/qpair.h> +#include <QtCore/qvariant.h> +#include <QtCore/qvector.h> +#include <QtCore/qstringlist.h> +#include <QtCore/qbitarray.h> +#include <QtCore/qmimedata.h> + +#include <private/qstandarditemmodel_p.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +class QStandardItemModelLessThan +{ +public: + inline QStandardItemModelLessThan() + { } + + inline bool operator()(const QPair<QStandardItem*, int> &l, + const QPair<QStandardItem*, int> &r) const + { + return *(l.first) < *(r.first); + } +}; + +class QStandardItemModelGreaterThan +{ +public: + inline QStandardItemModelGreaterThan() + { } + + inline bool operator()(const QPair<QStandardItem*, int> &l, + const QPair<QStandardItem*, int> &r) const + { + return *(r.first) < *(l.first); + } +}; + +/*! + \internal +*/ +QStandardItemPrivate::~QStandardItemPrivate() +{ + QVector<QStandardItem*>::const_iterator it; + for (it = children.constBegin(); it != children.constEnd(); ++it) { + QStandardItem *child = *it; + if (child) + child->d_func()->setModel(0); + delete child; + } + children.clear(); + if (parent && model) + parent->d_func()->childDeleted(q_func()); +} + +/*! + \internal +*/ +QPair<int, int> QStandardItemPrivate::position() const +{ + if (QStandardItem *par = parent) { + int idx = par->d_func()->childIndex(q_func()); + if (idx == -1) + return QPair<int, int>(-1, -1); + return QPair<int, int>(idx / par->columnCount(), idx % par->columnCount()); + } + // ### support header items? + return QPair<int, int>(-1, -1); +} + +/*! + \internal +*/ +void QStandardItemPrivate::setChild(int row, int column, QStandardItem *item, + bool emitChanged) +{ + Q_Q(QStandardItem); + if (item == q) { + qWarning("QStandardItem::setChild: Can't make an item a child of itself %p", + item); + return; + } + if ((row < 0) || (column < 0)) + return; + if (rows <= row) + q->setRowCount(row + 1); + if (columns <= column) + q->setColumnCount(column + 1); + int index = childIndex(row, column); + Q_ASSERT(index != -1); + QStandardItem *oldItem = children.at(index); + if (item == oldItem) + return; + if (item) { + if (item->d_func()->parent == 0) { + item->d_func()->setParentAndModel(q, model); + } else { + qWarning("QStandardItem::setChild: Ignoring duplicate insertion of item %p", + item); + return; + } + } + if (oldItem) + oldItem->d_func()->setModel(0); + delete oldItem; + children.replace(index, item); + if (emitChanged && model) + model->d_func()->itemChanged(item); +} + + +/*! + \internal +*/ +void QStandardItemPrivate::changeFlags(bool enable, Qt::ItemFlags f) +{ + Q_Q(QStandardItem); + Qt::ItemFlags flags = q->flags(); + if (enable) + flags |= f; + else + flags &= ~f; + q->setFlags(flags); +} + +/*! + \internal +*/ +void QStandardItemPrivate::childDeleted(QStandardItem *child) +{ + int index = childIndex(child); + Q_ASSERT(index != -1); + children.replace(index, 0); +} + +/*! + \internal +*/ +void QStandardItemPrivate::setItemData(const QMap<int, QVariant> &roles) +{ + Q_Q(QStandardItem); + + //let's build the vector of new values + QVector<QWidgetItemData> newValues; + QMap<int, QVariant>::const_iterator it; + for (it = roles.begin(); it != roles.end(); ++it) { + QVariant value = it.value(); + if (value.isValid()) { + int role = it.key(); + role = (role == Qt::EditRole) ? Qt::DisplayRole : role; + QWidgetItemData wid(role,it.value()); + newValues.append(wid); + } + } + + if (values!=newValues) { + values=newValues; + if (model) + model->d_func()->itemChanged(q); + } +} + +/*! + \internal +*/ +const QMap<int, QVariant> QStandardItemPrivate::itemData() const +{ + QMap<int, QVariant> result; + QVector<QWidgetItemData>::const_iterator it; + for (it = values.begin(); it != values.end(); ++it) + result.insert((*it).role, (*it).value); + return result; +} + +/*! + \internal +*/ +void QStandardItemPrivate::sortChildren(int column, Qt::SortOrder order) +{ + Q_Q(QStandardItem); + if (column >= columnCount()) + return; + + QVector<QPair<QStandardItem*, int> > sortable; + QVector<int> unsortable; + + sortable.reserve(rowCount()); + unsortable.reserve(rowCount()); + + for (int row = 0; row < rowCount(); ++row) { + QStandardItem *itm = q->child(row, column); + if (itm) + sortable.append(QPair<QStandardItem*,int>(itm, row)); + else + unsortable.append(row); + } + + if (order == Qt::AscendingOrder) { + QStandardItemModelLessThan lt; + qStableSort(sortable.begin(), sortable.end(), lt); + } else { + QStandardItemModelGreaterThan gt; + qStableSort(sortable.begin(), sortable.end(), gt); + } + + QModelIndexList changedPersistentIndexesFrom, changedPersistentIndexesTo; + QVector<QStandardItem*> sorted_children(children.count()); + for (int i = 0; i < rowCount(); ++i) { + int r = (i < sortable.count() + ? sortable.at(i).second + : unsortable.at(i - sortable.count())); + for (int c = 0; c < columnCount(); ++c) { + QStandardItem *itm = q->child(r, c); + sorted_children[childIndex(i, c)] = itm; + if (model) { + QModelIndex from = model->createIndex(r, c, q); + if (model->d_func()->persistent.indexes.contains(from)) { + QModelIndex to = model->createIndex(i, c, q); + changedPersistentIndexesFrom.append(from); + changedPersistentIndexesTo.append(to); + } + } + } + } + + children = sorted_children; + + if (model) { + model->changePersistentIndexList(changedPersistentIndexesFrom, changedPersistentIndexesTo); + } + + QVector<QStandardItem*>::iterator it; + for (it = children.begin(); it != children.end(); ++it) { + if (*it) + (*it)->d_func()->sortChildren(column, order); + } +} + +/*! + \internal + set the model of this item and all its children + */ +void QStandardItemPrivate::setModel(QStandardItemModel *mod) +{ + if (children.isEmpty()) { + if (model) + model->d_func()->invalidatePersistentIndex(model->indexFromItem(q_ptr)); + model = mod; + } else { + QStack<QStandardItem*> stack; + stack.push(q_ptr); + while (!stack.isEmpty()) { + QStandardItem *itm = stack.pop(); + if (itm->d_func()->model) { + itm->d_func()->model->d_func()->invalidatePersistentIndex(itm->d_func()->model->indexFromItem(itm)); + } + itm->d_func()->model = mod; + const QVector<QStandardItem*> &childList = itm->d_func()->children; + for (int i = 0; i < childList.count(); ++i) { + QStandardItem *chi = childList.at(i); + if (chi) + stack.push(chi); + } + } + } +} + +/*! + \internal +*/ +QStandardItemModelPrivate::QStandardItemModelPrivate() + : root(new QStandardItem), + itemPrototype(0), + sortRole(Qt::DisplayRole) +{ + root->setFlags(Qt::ItemIsDropEnabled); +} + +/*! + \internal +*/ +QStandardItemModelPrivate::~QStandardItemModelPrivate() +{ + delete root; + delete itemPrototype; + qDeleteAll(columnHeaderItems); + qDeleteAll(rowHeaderItems); +} + +/*! + \internal +*/ +void QStandardItemModelPrivate::init() +{ + Q_Q(QStandardItemModel); + QObject::connect(q, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + q, SLOT(_q_emitItemChanged(QModelIndex,QModelIndex))); +} + +/*! + \internal +*/ +void QStandardItemModelPrivate::_q_emitItemChanged(const QModelIndex &topLeft, + const QModelIndex &bottomRight) +{ + Q_Q(QStandardItemModel); + QModelIndex parent = topLeft.parent(); + for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { + for (int column = topLeft.column(); column <= bottomRight.column(); ++column) { + QModelIndex index = q->index(row, column, parent); + if (QStandardItem *item = itemFromIndex(index)) + emit q->itemChanged(item); + } + } +} + +/*! + \internal +*/ +bool QStandardItemPrivate::insertRows(int row, const QList<QStandardItem*> &items) +{ + Q_Q(QStandardItem); + if ((row < 0) || (row > rowCount())) + return false; + int count = items.count(); + if (model) + model->d_func()->rowsAboutToBeInserted(q, row, row + count - 1); + if (rowCount() == 0) { + if (columnCount() == 0) + q->setColumnCount(1); + children.resize(columnCount() * count); + rows = count; + } else { + rows += count; + int index = childIndex(row, 0); + if (index != -1) + children.insert(index, columnCount() * count, 0); + } + for (int i = 0; i < items.count(); ++i) { + QStandardItem *item = items.at(i); + item->d_func()->model = model; + item->d_func()->parent = q; + int index = childIndex(i + row, 0); + children.replace(index, item); + } + if (model) + model->d_func()->rowsInserted(q, row, count); + return true; +} + +bool QStandardItemPrivate::insertRows(int row, int count, const QList<QStandardItem*> &items) +{ + Q_Q(QStandardItem); + if ((count < 1) || (row < 0) || (row > rowCount())) + return false; + if (model) + model->d_func()->rowsAboutToBeInserted(q, row, row + count - 1); + if (rowCount() == 0) { + children.resize(columnCount() * count); + rows = count; + } else { + rows += count; + int index = childIndex(row, 0); + if (index != -1) + children.insert(index, columnCount() * count, 0); + } + if (!items.isEmpty()) { + int index = childIndex(row, 0); + int limit = qMin(items.count(), columnCount() * count); + for (int i = 0; i < limit; ++i) { + QStandardItem *item = items.at(i); + if (item) { + if (item->d_func()->parent == 0) { + item->d_func()->setParentAndModel(q, model); + } else { + qWarning("QStandardItem::insertRows: Ignoring duplicate insertion of item %p", + item); + item = 0; + } + } + children.replace(index, item); + ++index; + } + } + if (model) + model->d_func()->rowsInserted(q, row, count); + return true; +} + +/*! + \internal +*/ +bool QStandardItemPrivate::insertColumns(int column, int count, const QList<QStandardItem*> &items) +{ + Q_Q(QStandardItem); + if ((count < 1) || (column < 0) || (column > columnCount())) + return false; + if (model) + model->d_func()->columnsAboutToBeInserted(q, column, column + count - 1); + if (columnCount() == 0) { + children.resize(rowCount() * count); + columns = count; + } else { + columns += count; + int index = childIndex(0, column); + for (int row = 0; row < rowCount(); ++row) { + children.insert(index, count, 0); + index += columnCount(); + } + } + if (!items.isEmpty()) { + int limit = qMin(items.count(), rowCount() * count); + for (int i = 0; i < limit; ++i) { + QStandardItem *item = items.at(i); + if (item) { + if (item->d_func()->parent == 0) { + item->d_func()->setParentAndModel(q, model); + } else { + qWarning("QStandardItem::insertColumns: Ignoring duplicate insertion of item %p", + item); + item = 0; + } + } + int r = i / count; + int c = column + (i % count); + int index = childIndex(r, c); + children.replace(index, item); + } + } + if (model) + model->d_func()->columnsInserted(q, column, count); + return true; +} + +/*! + \internal +*/ +void QStandardItemModelPrivate::itemChanged(QStandardItem *item) +{ + Q_Q(QStandardItemModel); + if (item->d_func()->parent == 0) { + // Header item + int idx = columnHeaderItems.indexOf(item); + if (idx != -1) { + emit q->headerDataChanged(Qt::Horizontal, idx, idx); + } else { + idx = rowHeaderItems.indexOf(item); + if (idx != -1) + emit q->headerDataChanged(Qt::Vertical, idx, idx); + } + } else { + // Normal item + QModelIndex index = q->indexFromItem(item); + emit q->dataChanged(index, index); + } +} + +/*! + \internal +*/ +void QStandardItemModelPrivate::rowsAboutToBeInserted(QStandardItem *parent, + int start, int end) +{ + Q_Q(QStandardItemModel); + QModelIndex index = q->indexFromItem(parent); + q->beginInsertRows(index, start, end); +} + +/*! + \internal +*/ +void QStandardItemModelPrivate::columnsAboutToBeInserted(QStandardItem *parent, + int start, int end) +{ + Q_Q(QStandardItemModel); + QModelIndex index = q->indexFromItem(parent); + q->beginInsertColumns(index, start, end); +} + +/*! + \internal +*/ +void QStandardItemModelPrivate::rowsAboutToBeRemoved(QStandardItem *parent, + int start, int end) +{ + Q_Q(QStandardItemModel); + QModelIndex index = q->indexFromItem(parent); + q->beginRemoveRows(index, start, end); +} + +/*! + \internal +*/ +void QStandardItemModelPrivate::columnsAboutToBeRemoved(QStandardItem *parent, + int start, int end) +{ + Q_Q(QStandardItemModel); + QModelIndex index = q->indexFromItem(parent); + q->beginRemoveColumns(index, start, end); +} + +/*! + \internal +*/ +void QStandardItemModelPrivate::rowsInserted(QStandardItem *parent, + int row, int count) +{ + Q_Q(QStandardItemModel); + if (parent == root) + rowHeaderItems.insert(row, count, 0); + q->endInsertRows(); +} + +/*! + \internal +*/ +void QStandardItemModelPrivate::columnsInserted(QStandardItem *parent, + int column, int count) +{ + Q_Q(QStandardItemModel); + if (parent == root) + columnHeaderItems.insert(column, count, 0); + q->endInsertColumns(); +} + +/*! + \internal +*/ +void QStandardItemModelPrivate::rowsRemoved(QStandardItem *parent, + int row, int count) +{ + Q_Q(QStandardItemModel); + if (parent == root) { + for (int i = row; i < row + count; ++i) { + QStandardItem *oldItem = rowHeaderItems.at(i); + if (oldItem) + oldItem->d_func()->setModel(0); + delete oldItem; + } + rowHeaderItems.remove(row, count); + } + q->endRemoveRows(); +} + +/*! + \internal +*/ +void QStandardItemModelPrivate::columnsRemoved(QStandardItem *parent, + int column, int count) +{ + Q_Q(QStandardItemModel); + if (parent == root) { + for (int i = column; i < column + count; ++i) { + QStandardItem *oldItem = columnHeaderItems.at(i); + if (oldItem) + oldItem->d_func()->setModel(0); + delete oldItem; + } + columnHeaderItems.remove(column, count); + } + q->endRemoveColumns(); +} + +/*! + \class QStandardItem + \brief The QStandardItem class provides an item for use with the + QStandardItemModel class. + \since 4.2 + \ingroup model-view + + Items usually contain text, icons, or checkboxes. + + Each item can have its own background brush which is set with the + setBackground() function. The current background brush can be found with + background(). The text label for each item can be rendered with its own + font and brush. These are specified with the setFont() and setForeground() + functions, and read with font() and foreground(). + + By default, items are enabled, editable, selectable, checkable, and can be + used both as the source of a drag and drop operation and as a drop target. + Each item's flags can be changed by calling setFlags(). Checkable items + can be checked and unchecked with the setCheckState() function. The + corresponding checkState() function indicates whether the item is + currently checked. + + You can store application-specific data in an item by calling setData(). + + Each item can have a two-dimensional table of child items. This makes it + possible to build hierarchies of items. The typical hierarchy is the tree, + in which case the child table is a table with a single column (a list). + + The dimensions of the child table can be set with setRowCount() and + setColumnCount(). Items can be positioned in the child table with + setChild(). Get a pointer to a child item with child(). New rows and + columns of children can also be inserted with insertRow() and + insertColumn(), or appended with appendRow() and appendColumn(). When + using the append and insert functions, the dimensions of the child table + will grow as needed. + + An existing row of children can be removed with removeRow() or takeRow(); + correspondingly, a column can be removed with removeColumn() or + takeColumn(). + + An item's children can be sorted by calling sortChildren(). + + \section1 Subclassing + + When subclassing QStandardItem to provide custom items, it is possible to + define new types for them so that they can be distinguished from the base + class. The type() function should be reimplemented to return a new type + value equal to or greater than \l UserType. + + Reimplement data() and setData() if you want to perform custom handling of + data queries and/or control how an item's data is represented. + + Reimplement clone() if you want QStandardItemModel to be able to create + instances of your custom item class on demand (see + QStandardItemModel::setItemPrototype()). + + Reimplement read() and write() if you want to control how items are + represented in their serialized form. + + Reimplement \l{operator<()} if you want to control the semantics of item + comparison. \l{operator<()} determines the sorted order when sorting items + with sortChildren() or with QStandardItemModel::sort(). + + \sa QStandardItemModel, {Item View Convenience Classes}, {Model/View Programming} +*/ + +/*! + \enum QStandardItem::ItemType + + This enum describes the types that are used to describe standard items. + + \value Type The default type for standard items. + \value UserType The minimum value for custom types. Values below UserType are + reserved by Qt. + + You can define new user types in QStandardItem subclasses to ensure that + custom items are treated specially; for example, when items are sorted. + + \sa type() +*/ + +/*! + Constructs an item. +*/ +QStandardItem::QStandardItem() + : d_ptr(new QStandardItemPrivate) +{ + Q_D(QStandardItem); + d->q_ptr = this; +} + +/*! + Constructs an item with the given \a text. +*/ +QStandardItem::QStandardItem(const QString &text) + : d_ptr(new QStandardItemPrivate) +{ + Q_D(QStandardItem); + d->q_ptr = this; + setText(text); +} + +/*! + Constructs an item with the given \a icon and \a text. +*/ +QStandardItem::QStandardItem(const QIcon &icon, const QString &text) + : d_ptr(new QStandardItemPrivate) +{ + Q_D(QStandardItem); + d->q_ptr = this; + setIcon(icon); + setText(text); +} + +/*! + Constructs an item with \a rows rows and \a columns columns of child items. +*/ +QStandardItem::QStandardItem(int rows, int columns) + : d_ptr(new QStandardItemPrivate) +{ + Q_D(QStandardItem); + d->q_ptr = this; + setRowCount(rows); + setColumnCount(columns); +} + +/*! + \internal +*/ +QStandardItem::QStandardItem(QStandardItemPrivate &dd) + : d_ptr(&dd) +{ + Q_D(QStandardItem); + d->q_ptr = this; +} + +/*! + Constructs a copy of \a other. Note that model() is + not copied. + + This function is useful when reimplementing clone(). +*/ +QStandardItem::QStandardItem(const QStandardItem &other) + : d_ptr(new QStandardItemPrivate) +{ + Q_D(QStandardItem); + d->q_ptr = this; + operator=(other); +} + +/*! + Assigns \a other's data and flags to this item. Note that + type() and model() are not copied. + + This function is useful when reimplementing clone(). +*/ +QStandardItem &QStandardItem::operator=(const QStandardItem &other) +{ + Q_D(QStandardItem); + d->values = other.d_func()->values; + return *this; +} + +/*! + Destructs the item. + This causes the item's children to be destructed as well. +*/ +QStandardItem::~QStandardItem() +{ + Q_D(QStandardItem); + delete d; +} + +/*! + Returns the item's parent item, or 0 if the item has no parent. + + \sa child() +*/ +QStandardItem *QStandardItem::parent() const +{ + Q_D(const QStandardItem); + if (!d->model || (d->model->d_func()->root != d->parent)) + return d->parent; + return 0; +} + +/*! + Sets the item's data for the given \a role to the specified \a value. + + If you subclass QStandardItem and reimplement this function, your + reimplementation should call emitDataChanged() if you do not call + the base implementation of setData(). This will ensure that e.g. + views using the model are notified of the changes. + + \note The default implementation treats Qt::EditRole and Qt::DisplayRole + as referring to the same data. + + \sa Qt::ItemDataRole, data(), setFlags() +*/ +void QStandardItem::setData(const QVariant &value, int role) +{ + Q_D(QStandardItem); + role = (role == Qt::EditRole) ? Qt::DisplayRole : role; + QVector<QWidgetItemData>::iterator it; + for (it = d->values.begin(); it != d->values.end(); ++it) { + if ((*it).role == role) { + if (value.isValid()) { + if ((*it).value.type() == value.type() && (*it).value == value) + return; + (*it).value = value; + } else { + d->values.erase(it); + } + if (d->model) + d->model->d_func()->itemChanged(this); + return; + } + } + d->values.append(QWidgetItemData(role, value)); + if (d->model) + d->model->d_func()->itemChanged(this); +} + +/*! + Returns the item's data for the given \a role, or an invalid + QVariant if there is no data for the role. + + \note The default implementation treats Qt::EditRole and Qt::DisplayRole + as referring to the same data. +*/ +QVariant QStandardItem::data(int role) const +{ + Q_D(const QStandardItem); + role = (role == Qt::EditRole) ? Qt::DisplayRole : role; + QVector<QWidgetItemData>::const_iterator it; + for (it = d->values.begin(); it != d->values.end(); ++it) { + if ((*it).role == role) + return (*it).value; + } + return QVariant(); +} + +/*! + \since 4.4 + + Causes the model associated with this item to emit a + \l{QAbstractItemModel::dataChanged()}{dataChanged}() signal for this + item. + + You normally only need to call this function if you have subclassed + QStandardItem and reimplemented data() and/or setData(). + + \sa setData() +*/ +void QStandardItem::emitDataChanged() +{ + Q_D(QStandardItem); + if (d->model) + d->model->d_func()->itemChanged(this); +} + +/*! + Sets the item flags for the item to \a flags. + + The item flags determine how the user can interact with the item. + This is often used to disable an item. + + \sa flags(), setData() +*/ +void QStandardItem::setFlags(Qt::ItemFlags flags) +{ + setData((int)flags, Qt::UserRole - 1); +} + +/*! + Returns the item flags for the item. + + The item flags determine how the user can interact with the item. + + By default, items are enabled, editable, selectable, checkable, and can be + used both as the source of a drag and drop operation and as a drop target. + + \sa setFlags() +*/ +Qt::ItemFlags QStandardItem::flags() const +{ + QVariant v = data(Qt::UserRole - 1); + if (!v.isValid()) + return (Qt::ItemIsSelectable|Qt::ItemIsEnabled|Qt::ItemIsEditable + |Qt::ItemIsDragEnabled|Qt::ItemIsDropEnabled); + return ((Qt::ItemFlags)(v.toInt())); +} + +/*! + \fn QString QStandardItem::text() const + + Returns the item's text. This is the text that's presented to the user + in a view. + + \sa setText() +*/ + +/*! + \fn void QStandardItem::setText(const QString &text) + + Sets the item's text to the \a text specified. + + \sa text(), setFont(), setForeground() +*/ + +/*! + \fn QIcon QStandardItem::icon() const + + Returns the item's icon. + + \sa setIcon(), {QAbstractItemView::iconSize}{iconSize} +*/ + +/*! + \fn void QStandardItem::setIcon(const QIcon &icon) + + Sets the item's icon to the \a icon specified. +*/ + +/*! + \fn QString QStandardItem::statusTip() const + + Returns the item's status tip. + + \sa setStatusTip(), toolTip(), whatsThis() +*/ + +/*! + \fn void QStandardItem::setStatusTip(const QString &statusTip) + + Sets the item's status tip to the string specified by \a statusTip. + + \sa statusTip(), setToolTip(), setWhatsThis() +*/ + +/*! + \fn QString QStandardItem::toolTip() const + + Returns the item's tooltip. + + \sa setToolTip(), statusTip(), whatsThis() +*/ + +/*! + \fn void QStandardItem::setToolTip(const QString &toolTip) + + Sets the item's tooltip to the string specified by \a toolTip. + + \sa toolTip(), setStatusTip(), setWhatsThis() +*/ + +/*! + \fn QString QStandardItem::whatsThis() const + + Returns the item's "What's This?" help. + + \sa setWhatsThis(), toolTip(), statusTip() +*/ + +/*! + \fn void QStandardItem::setWhatsThis(const QString &whatsThis) + + Sets the item's "What's This?" help to the string specified by \a whatsThis. + + \sa whatsThis(), setStatusTip(), setToolTip() +*/ + +/*! + \fn QFont QStandardItem::font() const + + Returns the font used to render the item's text. + + \sa setFont() +*/ + +/*! + \fn void QStandardItem::setFont(const QFont &font) + + Sets the font used to display the item's text to the given \a font. + + \sa font() setText() setForeground() +*/ + +/*! + \fn QBrush QStandardItem::background() const + + Returns the brush used to render the item's background. + + \sa foreground() setBackground() +*/ + +/*! + \fn void QStandardItem::setBackground(const QBrush &brush) + + Sets the item's background brush to the specified \a brush. + + \sa background() setForeground() +*/ + +/*! + \fn QBrush QStandardItem::foreground() const + + Returns the brush used to render the item's foreground (e.g. text). + + \sa setForeground() background() +*/ + +/*! + \fn void QStandardItem::setForeground(const QBrush &brush) + + Sets the brush used to display the item's foreground (e.g. text) to the + given \a brush. + + \sa foreground() setBackground() setFont() +*/ + +/*! + \fn int QStandardItem::textAlignment() const + + Returns the text alignment for the item's text. +*/ + +/*! + \fn void QStandardItem::setTextAlignment(Qt::Alignment alignment) + + Sets the text alignment for the item's text to the \a alignment + specified. + + \sa textAlignment() +*/ + +/*! + \fn QSize QStandardItem::sizeHint() const + + Returns the size hint set for the item, or an invalid QSize if no + size hint has been set. + + If no size hint has been set, the item delegate will compute the + size hint based on the item data. + + \sa setSizeHint() +*/ + +/*! + \fn void QStandardItem::setSizeHint(const QSize &size) + + Sets the size hint for the item to be \a size. + If no size hint is set, the item delegate will compute the + size hint based on the item data. + + \sa sizeHint() +*/ + +/*! + \fn Qt::CheckState QStandardItem::checkState() const + + Returns the checked state of the item. + + \sa setCheckState(), isCheckable() +*/ + +/*! + \fn void QStandardItem::setCheckState(Qt::CheckState state) + + Sets the check state of the item to be \a state. + + \sa checkState(), setCheckable() +*/ + +/*! + \fn QString QStandardItem::accessibleText() const + + Returns the item's accessible text. + + The accessible text is used by assistive technologies (i.e. for users who + cannot use conventional means of interaction). + + \sa setAccessibleText(), accessibleDescription() +*/ + +/*! + \fn void QStandardItem::setAccessibleText(const QString &accessibleText) + + Sets the item's accessible text to the string specified by \a accessibleText. + + The accessible text is used by assistive technologies (i.e. for users who + cannot use conventional means of interaction). + + \sa accessibleText(), setAccessibleDescription() +*/ + +/*! + \fn QString QStandardItem::accessibleDescription() const + + Returns the item's accessible description. + + The accessible description is used by assistive technologies (i.e. for + users who cannot use conventional means of interaction). + + \sa setAccessibleDescription(), accessibleText() +*/ + +/*! + \fn void QStandardItem::setAccessibleDescription(const QString &accessibleDescription) + + Sets the item's accessible description to the string specified by \a + accessibleDescription. + + The accessible description is used by assistive technologies (i.e. for + users who cannot use conventional means of interaction). + + \sa accessibleDescription(), setAccessibleText() +*/ + +/*! + Sets whether the item is enabled. If \a enabled is true, the item is enabled, + meaning that the user can interact with the item; if \a enabled is false, the + user cannot interact with the item. + + This flag takes presedence over the other item flags; e.g. if an item is not + enabled, it cannot be selected by the user, even if the Qt::ItemIsSelectable + flag has been set. + + \sa isEnabled(), Qt::ItemIsEnabled, setFlags() +*/ +void QStandardItem::setEnabled(bool enabled) +{ + Q_D(QStandardItem); + d->changeFlags(enabled, Qt::ItemIsEnabled); +} + +/*! + \fn bool QStandardItem::isEnabled() const + + Returns whether the item is enabled. + + When an item is enabled, the user can interact with it. The possible + types of interaction are specified by the other item flags, such as + isEditable() and isSelectable(). + + The default value is true. + + \sa setEnabled(), flags() +*/ + +/*! + Sets whether the item is editable. If \a editable is true, the item can be + edited by the user; otherwise, the user cannot edit the item. + + How the user can edit items in a view is determined by the view's edit + triggers; see QAbstractItemView::editTriggers. + + \sa isEditable(), setFlags() +*/ +void QStandardItem::setEditable(bool editable) +{ + Q_D(QStandardItem); + d->changeFlags(editable, Qt::ItemIsEditable); +} + +/*! + \fn bool QStandardItem::isEditable() const + + Returns whether the item can be edited by the user. + + When an item is editable (and enabled), the user can edit the item by + invoking one of the view's edit triggers; see + QAbstractItemView::editTriggers. + + The default value is true. + + \sa setEditable(), flags() +*/ + +/*! + Sets whether the item is selectable. If \a selectable is true, the item + can be selected by the user; otherwise, the user cannot select the item. + + You can control the selection behavior and mode by manipulating their + view properties; see QAbstractItemView::selectionMode and + QAbstractItemView::selectionBehavior. + + \sa isSelectable(), setFlags() +*/ +void QStandardItem::setSelectable(bool selectable) +{ + Q_D(QStandardItem); + d->changeFlags(selectable, Qt::ItemIsSelectable); +} + +/*! + \fn bool QStandardItem::isSelectable() const + + Returns whether the item is selectable by the user. + + The default value is true. + + \sa setSelectable(), flags() +*/ + +/*! + Sets whether the item is user-checkable. If \a checkable is true, the + item can be checked by the user; otherwise, the user cannot check + the item. + + The item delegate will render a checkable item with a check box next to the + item's text. + + \sa isCheckable(), setCheckState(), setTristate() +*/ +void QStandardItem::setCheckable(bool checkable) +{ + Q_D(QStandardItem); + if (checkable && !isCheckable()) { + // make sure there's data for the checkstate role + if (!data(Qt::CheckStateRole).isValid()) + setData(Qt::Unchecked, Qt::CheckStateRole); + } + d->changeFlags(checkable, Qt::ItemIsUserCheckable); +} + +/*! + \fn bool QStandardItem::isCheckable() const + + Returns whether the item is user-checkable. + + The default value is false. + + \sa setCheckable(), checkState(), isTristate() +*/ + +/*! + Sets whether the item is tristate. If \a tristate is true, the + item is checkable with three separate states; otherwise, the item + is checkable with two states. (Note that this also requires that + the item is checkable; see isCheckable().) + + \sa isTristate(), setCheckable(), setCheckState() +*/ +void QStandardItem::setTristate(bool tristate) +{ + Q_D(QStandardItem); + d->changeFlags(tristate, Qt::ItemIsTristate); +} + +/*! + \fn bool QStandardItem::isTristate() const + + Returns whether the item is tristate; that is, if it's checkable with three + separate states. + + The default value is false. + + \sa setTristate(), isCheckable(), checkState() +*/ + +#ifndef QT_NO_DRAGANDDROP + +/*! + Sets whether the item is drag enabled. If \a dragEnabled is true, the item + can be dragged by the user; otherwise, the user cannot drag the item. + + Note that you also need to ensure that item dragging is enabled in the view; + see QAbstractItemView::dragEnabled. + + \sa isDragEnabled(), setDropEnabled(), setFlags() +*/ +void QStandardItem::setDragEnabled(bool dragEnabled) +{ + Q_D(QStandardItem); + d->changeFlags(dragEnabled, Qt::ItemIsDragEnabled); +} + +/*! + \fn bool QStandardItem::isDragEnabled() const + + Returns whether the item is drag enabled. An item that is drag enabled can + be dragged by the user. + + The default value is true. + + Note that item dragging must be enabled in the view for dragging to work; + see QAbstractItemView::dragEnabled. + + \sa setDragEnabled(), isDropEnabled(), flags() +*/ + +/*! + Sets whether the item is drop enabled. If \a dropEnabled is true, the item + can be used as a drop target; otherwise, it cannot. + + Note that you also need to ensure that drops are enabled in the view; see + QWidget::acceptDrops(); and that the model supports the desired drop actions; + see QAbstractItemModel::supportedDropActions(). + + \sa isDropEnabled(), setDragEnabled(), setFlags() +*/ +void QStandardItem::setDropEnabled(bool dropEnabled) +{ + Q_D(QStandardItem); + d->changeFlags(dropEnabled, Qt::ItemIsDropEnabled); +} + +/*! + \fn bool QStandardItem::isDropEnabled() const + + Returns whether the item is drop enabled. When an item is drop enabled, it + can be used as a drop target. + + The default value is true. + + \sa setDropEnabled(), isDragEnabled(), flags() +*/ + +#endif // QT_NO_DRAGANDDROP + +/*! + Returns the row where the item is located in its parent's child table, or + -1 if the item has no parent. + + \sa column(), parent() +*/ +int QStandardItem::row() const +{ + Q_D(const QStandardItem); + QPair<int, int> pos = d->position(); + return pos.first; +} + +/*! + Returns the column where the item is located in its parent's child table, + or -1 if the item has no parent. + + \sa row(), parent() +*/ +int QStandardItem::column() const +{ + Q_D(const QStandardItem); + QPair<int, int> pos = d->position(); + return pos.second; +} + +/*! + Returns the QModelIndex associated with this item. + + When you need to invoke item functionality in a QModelIndex-based API (e.g. + QAbstractItemView), you can call this function to obtain an index that + corresponds to the item's location in the model. + + If the item is not associated with a model, an invalid QModelIndex is + returned. + + \sa model(), QStandardItemModel::itemFromIndex() +*/ +QModelIndex QStandardItem::index() const +{ + Q_D(const QStandardItem); + return d->model ? d->model->indexFromItem(this) : QModelIndex(); +} + +/*! + Returns the QStandardItemModel that this item belongs to. + + If the item is not a child of another item that belongs to the model, this + function returns 0. + + \sa index() +*/ +QStandardItemModel *QStandardItem::model() const +{ + Q_D(const QStandardItem); + return d->model; +} + +/*! + Sets the number of child item rows to \a rows. If this is less than + rowCount(), the data in the unwanted rows is discarded. + + \sa rowCount(), setColumnCount() +*/ +void QStandardItem::setRowCount(int rows) +{ + int rc = rowCount(); + if (rc == rows) + return; + if (rc < rows) + insertRows(qMax(rc, 0), rows - rc); + else + removeRows(qMax(rows, 0), rc - rows); +} + +/*! + Returns the number of child item rows that the item has. + + \sa setRowCount(), columnCount() +*/ +int QStandardItem::rowCount() const +{ + Q_D(const QStandardItem); + return d->rowCount(); +} + +/*! + Sets the number of child item columns to \a columns. If this is less than + columnCount(), the data in the unwanted columns is discarded. + + \sa columnCount(), setRowCount() +*/ +void QStandardItem::setColumnCount(int columns) +{ + int cc = columnCount(); + if (cc == columns) + return; + if (cc < columns) + insertColumns(qMax(cc, 0), columns - cc); + else + removeColumns(qMax(columns, 0), cc - columns); +} + +/*! + Returns the number of child item columns that the item has. + + \sa setColumnCount(), rowCount() +*/ +int QStandardItem::columnCount() const +{ + Q_D(const QStandardItem); + return d->columnCount(); +} + +/*! + Inserts a row at \a row containing \a items. If necessary, the column + count is increased to the size of \a items. + + \sa insertRows(), insertColumn() +*/ +void QStandardItem::insertRow(int row, const QList<QStandardItem*> &items) +{ + Q_D(QStandardItem); + if (row < 0) + return; + if (columnCount() < items.count()) + setColumnCount(items.count()); + d->insertRows(row, 1, items); +} + +/*! + Inserts \a items at \a row. The column count wont be changed. + + \sa insertRow(), insertColumn() +*/ +void QStandardItem::insertRows(int row, const QList<QStandardItem*> &items) +{ + Q_D(QStandardItem); + if (row < 0) + return; + d->insertRows(row, items); +} + +/*! + Inserts a column at \a column containing \a items. If necessary, + the row count is increased to the size of \a items. + + \sa insertColumns(), insertRow() +*/ +void QStandardItem::insertColumn(int column, const QList<QStandardItem*> &items) +{ + Q_D(QStandardItem); + if (column < 0) + return; + if (rowCount() < items.count()) + setRowCount(items.count()); + d->insertColumns(column, 1, items); +} + +/*! + Inserts \a count rows of child items at row \a row. + + \sa insertRow(), insertColumns() +*/ +void QStandardItem::insertRows(int row, int count) +{ + Q_D(QStandardItem); + if (rowCount() < row) { + count += row - rowCount(); + row = rowCount(); + } + d->insertRows(row, count, QList<QStandardItem*>()); +} + +/*! + Inserts \a count columns of child items at column \a column. + + \sa insertColumn(), insertRows() +*/ +void QStandardItem::insertColumns(int column, int count) +{ + Q_D(QStandardItem); + if (columnCount() < column) { + count += column - columnCount(); + column = columnCount(); + } + d->insertColumns(column, count, QList<QStandardItem*>()); +} + +/*! + \fn void QStandardItem::appendRow(const QList<QStandardItem*> &items) + + Appends a row containing \a items. If necessary, the column count is + increased to the size of \a items. + + \sa insertRow() +*/ + +/*! + \fn void QStandardItem::appendRows(const QList<QStandardItem*> &items) + + Appends rows containing \a items. The column count will not change. + + \sa insertRow() +*/ + +/*! + \fn void QStandardItem::appendColumn(const QList<QStandardItem*> &items) + + Appends a column containing \a items. If necessary, the row count is + increased to the size of \a items. + + \sa insertColumn() +*/ + +/*! + \fn bool QStandardItemModel::insertRow(int row, const QModelIndex &parent) + + Inserts a single row before the given \a row in the child items of the + \a parent specified. Returns true if the row is inserted; otherwise + returns false. + + \sa insertRows(), insertColumn(), removeRow() +*/ + +/*! + \fn bool QStandardItemModel::insertColumn(int column, const QModelIndex &parent) + + Inserts a single column before the given \a column in the child items of + the \a parent specified. Returns true if the column is inserted; otherwise + returns false. + + \sa insertColumns(), insertRow(), removeColumn() +*/ + +/*! + \fn QStandardItem::insertRow(int row, QStandardItem *item) + \overload + + Inserts a row at \a row containing \a item. + + When building a list or a tree that has only one column, this function + provides a convenient way to insert a single new item. +*/ + +/*! + \fn QStandardItem::appendRow(QStandardItem *item) + \overload + + Appends a row containing \a item. + + When building a list or a tree that has only one column, this function + provides a convenient way to append a single new item. +*/ + +/*! + Removes the given \a row. The items that were in the row are deleted. + + \sa takeRow(), removeRows(), removeColumn() +*/ +void QStandardItem::removeRow(int row) +{ + removeRows(row, 1); +} + +/*! + Removes the given \a column. The items that were in the + column are deleted. + + \sa takeColumn(), removeColumns(), removeRow() +*/ +void QStandardItem::removeColumn(int column) +{ + removeColumns(column, 1); +} + +/*! + Removes \a count rows at row \a row. The items that were in those rows are + deleted. + + \sa removeRow(), removeColumn() +*/ +void QStandardItem::removeRows(int row, int count) +{ + Q_D(QStandardItem); + if ((count < 1) || (row < 0) || ((row + count) > rowCount())) + return; + if (d->model) + d->model->d_func()->rowsAboutToBeRemoved(this, row, row + count - 1); + int i = d->childIndex(row, 0); + int n = count * d->columnCount(); + for (int j = i; j < n+i; ++j) { + QStandardItem *oldItem = d->children.at(j); + if (oldItem) + oldItem->d_func()->setModel(0); + delete oldItem; + } + d->children.remove(qMax(i, 0), n); + d->rows -= count; + if (d->model) + d->model->d_func()->rowsRemoved(this, row, count); +} + +/*! + Removes \a count columns at column \a column. The items that were in those + columns are deleted. + + \sa removeColumn(), removeRows() +*/ +void QStandardItem::removeColumns(int column, int count) +{ + Q_D(QStandardItem); + if ((count < 1) || (column < 0) || ((column + count) > columnCount())) + return; + if (d->model) + d->model->d_func()->columnsAboutToBeRemoved(this, column, column + count - 1); + for (int row = d->rowCount() - 1; row >= 0; --row) { + int i = d->childIndex(row, column); + for (int j=i; j<i+count; ++j) { + QStandardItem *oldItem = d->children.at(j); + if (oldItem) + oldItem->d_func()->setModel(0); + delete oldItem; + } + d->children.remove(i, count); + } + d->columns -= count; + if (d->model) + d->model->d_func()->columnsRemoved(this, column, count); +} + +/*! + Returns true if this item has any children; otherwise returns false. + + \sa rowCount(), columnCount(), child() +*/ +bool QStandardItem::hasChildren() const +{ + return (rowCount() > 0) && (columnCount() > 0); +} + +/*! + Sets the child item at (\a row, \a column) to \a item. This item (the parent + item) takes ownership of \a item. If necessary, the row count and column + count are increased to fit the item. + + \sa child() +*/ +void QStandardItem::setChild(int row, int column, QStandardItem *item) +{ + Q_D(QStandardItem); + d->setChild(row, column, item, true); +} + +/*! + \fn QStandardItem::setChild(int row, QStandardItem *item) + \overload + + Sets the child at \a row to \a item. +*/ + +/*! + Returns the child item at (\a row, \a column) if one has been set; otherwise + returns 0. + + \sa setChild(), takeChild(), parent() +*/ +QStandardItem *QStandardItem::child(int row, int column) const +{ + Q_D(const QStandardItem); + int index = d->childIndex(row, column); + if (index == -1) + return 0; + return d->children.at(index); +} + +/*! + Removes the child item at (\a row, \a column) without deleting it, and returns + a pointer to the item. If there was no child at the given location, then + this function returns 0. + + Note that this function, unlike takeRow() and takeColumn(), does not affect + the dimensions of the child table. + + \sa child(), takeRow(), takeColumn() +*/ +QStandardItem *QStandardItem::takeChild(int row, int column) +{ + Q_D(QStandardItem); + QStandardItem *item = 0; + int index = d->childIndex(row, column); + if (index != -1) { + item = d->children.at(index); + if (item) + item->d_func()->setParentAndModel(0, 0); + d->children.replace(index, 0); + } + return item; +} + +/*! + Removes \a row without deleting the row items, and returns a list of + pointers to the removed items. For items in the row that have not been + set, the corresponding pointers in the list will be 0. + + \sa removeRow(), insertRow(), takeColumn() +*/ +QList<QStandardItem*> QStandardItem::takeRow(int row) +{ + Q_D(QStandardItem); + if ((row < 0) || (row >= rowCount())) + return QList<QStandardItem*>(); + QList<QStandardItem*> items; + int index = d->childIndex(row, 0); + for (int column = 0; column < d->columnCount(); ++column) { + QStandardItem *ch = d->children.at(index); + if (ch) { + ch->d_func()->setParentAndModel(0, 0); + d->children.replace(index, 0); + } + items.append(ch); + ++index; + } + removeRow(row); + return items; +} + +/*! + Removes \a column without deleting the column items, and returns a list of + pointers to the removed items. For items in the column that have not been + set, the corresponding pointers in the list will be 0. + + \sa removeColumn(), insertColumn(), takeRow() +*/ +QList<QStandardItem*> QStandardItem::takeColumn(int column) +{ + Q_D(QStandardItem); + if ((column < 0) || (column >= columnCount())) + return QList<QStandardItem*>(); + QList<QStandardItem*> items; + int index = d->childIndex(0, column); + for (int row = 0; row < d->rowCount(); ++row) { + QStandardItem *ch = d->children.at(index); + if (ch) { + ch->d_func()->setParentAndModel(0, 0); + d->children.replace(index, 0); + } + items.append(ch); + index += d->columnCount(); + } + removeColumn(column); + return items; +} + +/*! + Returns true if this item is less than \a other; otherwise returns false. + + The default implementation uses the data for the item's sort role (see + QStandardItemModel::sortRole) to perform the comparison if the item + belongs to a model; otherwise, the data for the item's Qt::DisplayRole + (text()) is used to perform the comparison. + + sortChildren() and QStandardItemModel::sort() use this function when + sorting items. If you want custom sorting, you can subclass QStandardItem + and reimplement this function. +*/ +bool QStandardItem::operator<(const QStandardItem &other) const +{ + const int role = model() ? model()->sortRole() : Qt::DisplayRole; + const QVariant l = data(role), r = other.data(role); + // this code is copied from QSortFilterProxyModel::lessThan() + switch (l.type()) { + case QVariant::Invalid: + return (r.type() == QVariant::Invalid); + case QVariant::Int: + return l.toInt() < r.toInt(); + case QVariant::UInt: + return l.toUInt() < r.toUInt(); + case QVariant::LongLong: + return l.toLongLong() < r.toLongLong(); + case QVariant::ULongLong: + return l.toULongLong() < r.toULongLong(); + case QVariant::Double: + return l.toDouble() < r.toDouble(); + case QVariant::Char: + return l.toChar() < r.toChar(); + case QVariant::Date: + return l.toDate() < r.toDate(); + case QVariant::Time: + return l.toTime() < r.toTime(); + case QVariant::DateTime: + return l.toDateTime() < r.toDateTime(); + case QVariant::String: + default: + return l.toString().compare(r.toString()) < 0; + } +} + +/*! + Sorts the children of the item using the given \a order, by the values in + the given \a column. + + \note This function is recursive, therefore it sorts the children of the + item, its grandchildren, etc. + + \sa {operator<()} +*/ +void QStandardItem::sortChildren(int column, Qt::SortOrder order) +{ + Q_D(QStandardItem); + if ((column < 0) || (rowCount() == 0)) + return; + if (d->model) + emit d->model->layoutAboutToBeChanged(); + d->sortChildren(column, order); + if (d->model) + emit d->model->layoutChanged(); +} + +/*! + Returns a copy of this item. The item's children are not copied. + + When subclassing QStandardItem, you can reimplement this function + to provide QStandardItemModel with a factory that it can use to + create new items on demand. + + \sa QStandardItemModel::setItemPrototype(), operator=() +*/ +QStandardItem *QStandardItem::clone() const +{ + return new QStandardItem(*this); +} + +/*! + Returns the type of this item. The type is used to distinguish custom + items from the base class. When subclassing QStandardItem, you should + reimplement this function and return a new value greater than or equal + to \l UserType. + + \sa QStandardItem::Type +*/ +int QStandardItem::type() const +{ + return Type; +} + +#ifndef QT_NO_DATASTREAM + +/*! + Reads the item from stream \a in. Only the data and flags of the item are + read, not the child items. + + \sa write() +*/ +void QStandardItem::read(QDataStream &in) +{ + Q_D(QStandardItem); + in >> d->values; + qint32 flags; + in >> flags; + setFlags((Qt::ItemFlags)flags); +} + +/*! + Writes the item to stream \a out. Only the data and flags of the item + are written, not the child items. + + \sa read() +*/ +void QStandardItem::write(QDataStream &out) const +{ + Q_D(const QStandardItem); + out << d->values; + out << flags(); +} + +/*! + \relates QStandardItem + \since 4.2 + + Reads a QStandardItem from stream \a in into \a item. + + This operator uses QStandardItem::read(). + + \sa {Format of the QDataStream Operators} +*/ +QDataStream &operator>>(QDataStream &in, QStandardItem &item) +{ + item.read(in); + return in; +} + +/*! + \relates QStandardItem + \since 4.2 + + Writes the QStandardItem \a item to stream \a out. + + This operator uses QStandardItem::write(). + + \sa {Format of the QDataStream Operators} +*/ +QDataStream &operator<<(QDataStream &out, const QStandardItem &item) +{ + item.write(out); + return out; +} + +#endif // !QT_NO_DATASTREAM + +/*! + \class QStandardItemModel + \brief The QStandardItemModel class provides a generic model for storing custom data. + \ingroup model-view + + QStandardItemModel can be used as a repository for standard Qt + data types. It is one of the \l {Model/View Classes} and is part + of Qt's \l {Model/View Programming}{model/view} framework. + + QStandardItemModel provides a classic item-based approach to working with + the model. The items in a QStandardItemModel are provided by + QStandardItem. + + QStandardItemModel implements the QAbstractItemModel interface, which + means that the model can be used to provide data in any view that supports + that interface (such as QListView, QTableView and QTreeView, and your own + custom views). For performance and flexibility, you may want to subclass + QAbstractItemModel to provide support for different kinds of data + repositories. For example, the QDirModel provides a model interface to the + underlying file system. + + When you want a list or tree, you typically create an empty + QStandardItemModel and use appendRow() to add items to the model, and + item() to access an item. If your model represents a table, you typically + pass the dimensions of the table to the QStandardItemModel constructor and + use setItem() to position items into the table. You can also use setRowCount() + and setColumnCount() to alter the dimensions of the model. To insert items, + use insertRow() or insertColumn(), and to remove items, use removeRow() or + removeColumn(). + + You can set the header labels of your model with setHorizontalHeaderLabels() + and setVerticalHeaderLabels(). + + You can search for items in the model with findItems(), and sort the model by + calling sort(). + + Call clear() to remove all items from the model. + + An example usage of QStandardItemModel to create a table: + + \snippet doc/src/snippets/code/src_gui_itemviews_qstandarditemmodel.cpp 0 + + An example usage of QStandardItemModel to create a tree: + + \snippet doc/src/snippets/code/src_gui_itemviews_qstandarditemmodel.cpp 1 + + After setting the model on a view, you typically want to react to user + actions, such as an item being clicked. Since a QAbstractItemView provides + QModelIndex-based signals and functions, you need a way to obtain the + QStandardItem that corresponds to a given QModelIndex, and vice + versa. itemFromIndex() and indexFromItem() provide this mapping. Typical + usage of itemFromIndex() includes obtaining the item at the current index + in a view, and obtaining the item that corresponds to an index carried by + a QAbstractItemView signal, such as QAbstractItemView::clicked(). First + you connect the view's signal to a slot in your class: + + \snippet doc/src/snippets/code/src_gui_itemviews_qstandarditemmodel.cpp 2 + + When you receive the signal, you call itemFromIndex() on the given model + index to get a pointer to the item: + + \snippet doc/src/snippets/code/src_gui_itemviews_qstandarditemmodel.cpp 3 + + Conversely, you must obtain the QModelIndex of an item when you want to + invoke a model/view function that takes an index as argument. You can + obtain the index either by using the model's indexFromItem() function, or, + equivalently, by calling QStandardItem::index(): + + \snippet doc/src/snippets/code/src_gui_itemviews_qstandarditemmodel.cpp 4 + + You are, of course, not required to use the item-based approach; you could + instead rely entirely on the QAbstractItemModel interface when working with + the model, or use a combination of the two as appropriate. + + \sa QStandardItem, {Model/View Programming}, QAbstractItemModel, + {itemviews/simpletreemodel}{Simple Tree Model example}, + {Item View Convenience Classes} +*/ + +/*! + \fn void QStandardItemModel::itemChanged(QStandardItem *item) + \since 4.2 + + This signal is emitted whenever the data of \a item has changed. +*/ + +/*! + Constructs a new item model with the given \a parent. +*/ +QStandardItemModel::QStandardItemModel(QObject *parent) + : QAbstractItemModel(*new QStandardItemModelPrivate, parent) +{ + Q_D(QStandardItemModel); + d->init(); + d->root->d_func()->setModel(this); +} + +/*! + Constructs a new item model that initially has \a rows rows and \a columns + columns, and that has the given \a parent. +*/ +QStandardItemModel::QStandardItemModel(int rows, int columns, QObject *parent) + : QAbstractItemModel(*new QStandardItemModelPrivate, parent) +{ + Q_D(QStandardItemModel); + d->init(); + d->root->insertColumns(0, columns); + d->columnHeaderItems.insert(0, columns, 0); + d->root->insertRows(0, rows); + d->rowHeaderItems.insert(0, rows, 0); + d->root->d_func()->setModel(this); +} + +/*! + \internal +*/ +QStandardItemModel::QStandardItemModel(QStandardItemModelPrivate &dd, QObject *parent) + : QAbstractItemModel(dd, parent) +{ + Q_D(QStandardItemModel); + d->init(); +} + +/*! + Destructs the model. The model destroys all its items. +*/ +QStandardItemModel::~QStandardItemModel() +{ +} + +/*! + Removes all items (including header items) from the model and sets the + number of rows and columns to zero. + + \sa removeColumns(), removeRows() +*/ +void QStandardItemModel::clear() +{ + Q_D(QStandardItemModel); + delete d->root; + d->root = new QStandardItem; + d->root->d_func()->setModel(this); + qDeleteAll(d->columnHeaderItems); + d->columnHeaderItems.clear(); + qDeleteAll(d->rowHeaderItems); + d->rowHeaderItems.clear(); + reset(); +} + +/*! + \since 4.2 + + Returns a pointer to the QStandardItem associated with the given \a index. + + Calling this function is typically the initial step when processing + QModelIndex-based signals from a view, such as + QAbstractItemView::activated(). In your slot, you call itemFromIndex(), + with the QModelIndex carried by the signal as argument, to obtain a + pointer to the corresponding QStandardItem. + + Note that this function will lazily create an item for the index (using + itemPrototype()), and set it in the parent item's child table, if no item + already exists at that index. + + If \a index is an invalid index, this function returns 0. + + \sa indexFromItem() +*/ +QStandardItem *QStandardItemModel::itemFromIndex(const QModelIndex &index) const +{ + Q_D(const QStandardItemModel); + if ((index.row() < 0) || (index.column() < 0) || (index.model() != this)) + return 0; + QStandardItem *parent = static_cast<QStandardItem*>(index.internalPointer()); + if (parent == 0) + return 0; + QStandardItem *item = parent->child(index.row(), index.column()); + // lazy part + if (item == 0) { + item = d->createItem(); + parent->d_func()->setChild(index.row(), index.column(), item); + } + return item; +} + +/*! + \since 4.2 + + Returns the QModelIndex associated with the given \a item. + + Use this function when you want to perform an operation that requires the + QModelIndex of the item, such as + QAbstractItemView::scrollTo(). QStandardItem::index() is provided as + convenience; it is equivalent to calling this function. + + \sa itemFromIndex(), QStandardItem::index() +*/ +QModelIndex QStandardItemModel::indexFromItem(const QStandardItem *item) const +{ + if (item && item->d_func()->parent) { + QPair<int, int> pos = item->d_func()->position(); + return createIndex(pos.first, pos.second, item->d_func()->parent); + } + return QModelIndex(); +} + +/*! + \since 4.2 + + Sets the number of rows in this model to \a rows. If + this is less than rowCount(), the data in the unwanted rows + is discarded. + + \sa setColumnCount() +*/ +void QStandardItemModel::setRowCount(int rows) +{ + Q_D(QStandardItemModel); + d->root->setRowCount(rows); +} + +/*! + \since 4.2 + + Sets the number of columns in this model to \a columns. If + this is less than columnCount(), the data in the unwanted columns + is discarded. + + \sa setRowCount() +*/ +void QStandardItemModel::setColumnCount(int columns) +{ + Q_D(QStandardItemModel); + d->root->setColumnCount(columns); +} + +/*! + \since 4.2 + + Sets the item for the given \a row and \a column to \a item. The model + takes ownership of the item. If necessary, the row count and column count + are increased to fit the item. The previous item at the given location (if + there was one) is deleted. + + \sa item() +*/ +void QStandardItemModel::setItem(int row, int column, QStandardItem *item) +{ + Q_D(QStandardItemModel); + d->root->d_func()->setChild(row, column, item, true); +} + +/*! + \fn QStandardItemModel::setItem(int row, QStandardItem *item) + \overload +*/ + +/*! + \since 4.2 + + Returns the item for the given \a row and \a column if one has been set; + otherwise returns 0. + + \sa setItem(), takeItem(), itemFromIndex() +*/ +QStandardItem *QStandardItemModel::item(int row, int column) const +{ + Q_D(const QStandardItemModel); + return d->root->child(row, column); +} + +/*! + \since 4.2 + + Returns the model's invisible root item. + + The invisible root item provides access to the model's top-level items + through the QStandardItem API, making it possible to write functions that + can treat top-level items and their children in a uniform way; for + example, recursive functions involving a tree model. + + \note Calling \l{QAbstractItemModel::index()}{index()} on the QStandardItem object + retrieved from this function is not valid. +*/ +QStandardItem *QStandardItemModel::invisibleRootItem() const +{ + Q_D(const QStandardItemModel); + return d->root; +} + +/*! + \since 4.2 + + Sets the horizontal header item for \a column to \a item. The model takes + ownership of the item. If necessary, the column count is increased to fit + the item. The previous header item (if there was one) is deleted. + + \sa horizontalHeaderItem(), setHorizontalHeaderLabels(), + setVerticalHeaderItem() +*/ +void QStandardItemModel::setHorizontalHeaderItem(int column, QStandardItem *item) +{ + Q_D(QStandardItemModel); + if (column < 0) + return; + if (columnCount() <= column) + setColumnCount(column + 1); + + QStandardItem *oldItem = d->columnHeaderItems.at(column); + if (item == oldItem) + return; + + if (item) { + if (item->model() == 0) { + item->d_func()->setModel(this); + } else { + qWarning("QStandardItem::setHorizontalHeaderItem: Ignoring duplicate insertion of item %p", + item); + return; + } + } + + if (oldItem) + oldItem->d_func()->setModel(0); + delete oldItem; + + d->columnHeaderItems.replace(column, item); + emit headerDataChanged(Qt::Horizontal, column, column); +} + +/*! + \since 4.2 + + Returns the horizontal header item for \a column if one has been set; + otherwise returns 0. + + \sa setHorizontalHeaderItem(), verticalHeaderItem() +*/ +QStandardItem *QStandardItemModel::horizontalHeaderItem(int column) const +{ + Q_D(const QStandardItemModel); + if ((column < 0) || (column >= columnCount())) + return 0; + return d->columnHeaderItems.at(column); +} + +/*! + \since 4.2 + + Sets the vertical header item for \a row to \a item. The model takes + ownership of the item. If necessary, the row count is increased to fit the + item. The previous header item (if there was one) is deleted. + + \sa verticalHeaderItem(), setVerticalHeaderLabels(), + setHorizontalHeaderItem() +*/ +void QStandardItemModel::setVerticalHeaderItem(int row, QStandardItem *item) +{ + Q_D(QStandardItemModel); + if (row < 0) + return; + if (rowCount() <= row) + setRowCount(row + 1); + + QStandardItem *oldItem = d->rowHeaderItems.at(row); + if (item == oldItem) + return; + + if (item) { + if (item->model() == 0) { + item->d_func()->setModel(this); + } else { + qWarning("QStandardItem::setVerticalHeaderItem: Ignoring duplicate insertion of item %p", + item); + return; + } + } + + if (oldItem) + oldItem->d_func()->setModel(0); + delete oldItem; + + d->rowHeaderItems.replace(row, item); + emit headerDataChanged(Qt::Vertical, row, row); +} + +/*! + \since 4.2 + + Returns the vertical header item for row \a row if one has been set; + otherwise returns 0. + + \sa setVerticalHeaderItem(), horizontalHeaderItem() +*/ +QStandardItem *QStandardItemModel::verticalHeaderItem(int row) const +{ + Q_D(const QStandardItemModel); + if ((row < 0) || (row >= rowCount())) + return 0; + return d->rowHeaderItems.at(row); +} + +/*! + \since 4.2 + + Sets the horizontal header labels using \a labels. If necessary, the + column count is increased to the size of \a labels. + + \sa setHorizontalHeaderItem() +*/ +void QStandardItemModel::setHorizontalHeaderLabels(const QStringList &labels) +{ + Q_D(QStandardItemModel); + if (columnCount() < labels.count()) + setColumnCount(labels.count()); + for (int i = 0; i < labels.count(); ++i) { + QStandardItem *item = horizontalHeaderItem(i); + if (!item) { + item = d->createItem(); + setHorizontalHeaderItem(i, item); + } + item->setText(labels.at(i)); + } +} + +/*! + \since 4.2 + + Sets the vertical header labels using \a labels. If necessary, the row + count is increased to the size of \a labels. + + \sa setVerticalHeaderItem() +*/ +void QStandardItemModel::setVerticalHeaderLabels(const QStringList &labels) +{ + Q_D(QStandardItemModel); + if (rowCount() < labels.count()) + setRowCount(labels.count()); + for (int i = 0; i < labels.count(); ++i) { + QStandardItem *item = verticalHeaderItem(i); + if (!item) { + item = d->createItem(); + setVerticalHeaderItem(i, item); + } + item->setText(labels.at(i)); + } +} + +/*! + \since 4.2 + + Sets the item prototype for the model to the specified \a item. The model + takes ownership of the prototype. + + The item prototype acts as a QStandardItem factory, by relying on the + QStandardItem::clone() function. To provide your own prototype, subclass + QStandardItem, reimplement QStandardItem::clone() and set the prototype to + be an instance of your custom class. Whenever QStandardItemModel needs to + create an item on demand (for instance, when a view or item delegate calls + setData())), the new items will be instances of your custom class. + + \sa itemPrototype(), QStandardItem::clone() +*/ +void QStandardItemModel::setItemPrototype(const QStandardItem *item) +{ + Q_D(QStandardItemModel); + if (d->itemPrototype != item) { + delete d->itemPrototype; + d->itemPrototype = item; + } +} + +/*! + \since 4.2 + + Returns the item prototype used by the model. The model uses the item + prototype as an item factory when it needs to construct new items on + demand (for instance, when a view or item delegate calls setData()). + + \sa setItemPrototype() +*/ +const QStandardItem *QStandardItemModel::itemPrototype() const +{ + Q_D(const QStandardItemModel); + return d->itemPrototype; +} + +/*! + \since 4.2 + + Returns a list of items that match the given \a text, using the given \a + flags, in the given \a column. +*/ +QList<QStandardItem*> QStandardItemModel::findItems(const QString &text, + Qt::MatchFlags flags, int column) const +{ + QModelIndexList indexes = match(index(0, column, QModelIndex()), + Qt::DisplayRole, text, -1, flags); + QList<QStandardItem*> items; + for (int i = 0; i < indexes.size(); ++i) + items.append(itemFromIndex(indexes.at(i))); + return items; +} + +/*! + \since 4.2 + + Appends a row containing \a items. If necessary, the column count is + increased to the size of \a items. + + \sa insertRow(), appendColumn() +*/ +void QStandardItemModel::appendRow(const QList<QStandardItem*> &items) +{ + invisibleRootItem()->appendRow(items); +} + +/*! + \since 4.2 + + Appends a column containing \a items. If necessary, the row count is + increased to the size of \a items. + + \sa insertColumn(), appendRow() +*/ +void QStandardItemModel::appendColumn(const QList<QStandardItem*> &items) +{ + invisibleRootItem()->appendColumn(items); +} + +/*! + \since 4.2 + \fn QStandardItemModel::appendRow(QStandardItem *item) + \overload + + When building a list or a tree that has only one column, this function + provides a convenient way to append a single new \a item. +*/ + +/*! + \since 4.2 + + Inserts a row at \a row containing \a items. If necessary, the column + count is increased to the size of \a items. + + \sa takeRow(), appendRow(), insertColumn() +*/ +void QStandardItemModel::insertRow(int row, const QList<QStandardItem*> &items) +{ + invisibleRootItem()->insertRow(row, items); +} + +/*! + \since 4.2 + + \fn void QStandardItemModel::insertRow(int row, QStandardItem *item) + \overload + + Inserts a row at \a row containing \a item. + + When building a list or a tree that has only one column, this function + provides a convenient way to append a single new item. +*/ + +/*! + \since 4.2 + + Inserts a column at \a column containing \a items. If necessary, the row + count is increased to the size of \a items. + + \sa takeColumn(), appendColumn(), insertRow() +*/ +void QStandardItemModel::insertColumn(int column, const QList<QStandardItem*> &items) +{ + invisibleRootItem()->insertColumn(column, items); +} + +/*! + \since 4.2 + + Removes the item at (\a row, \a column) without deleting it. The model + releases ownership of the item. + + \sa item(), takeRow(), takeColumn() +*/ +QStandardItem *QStandardItemModel::takeItem(int row, int column) +{ + Q_D(QStandardItemModel); + return d->root->takeChild(row, column); +} + +/*! + \since 4.2 + + Removes the given \a row without deleting the row items, and returns a + list of pointers to the removed items. The model releases ownership of the + items. For items in the row that have not been set, the corresponding + pointers in the list will be 0. + + \sa takeColumn() +*/ +QList<QStandardItem*> QStandardItemModel::takeRow(int row) +{ + Q_D(QStandardItemModel); + return d->root->takeRow(row); +} + +/*! + \since 4.2 + + Removes the given \a column without deleting the column items, and returns + a list of pointers to the removed items. The model releases ownership of + the items. For items in the column that have not been set, the + corresponding pointers in the list will be 0. + + \sa takeRow() +*/ +QList<QStandardItem*> QStandardItemModel::takeColumn(int column) +{ + Q_D(QStandardItemModel); + return d->root->takeColumn(column); +} + +/*! + \since 4.2 + + Removes the horizontal header item at \a column from the header without + deleting it, and returns a pointer to the item. The model releases + ownership of the item. + + \sa horizontalHeaderItem(), takeVerticalHeaderItem() +*/ +QStandardItem *QStandardItemModel::takeHorizontalHeaderItem(int column) +{ + Q_D(QStandardItemModel); + if ((column < 0) || (column >= columnCount())) + return 0; + QStandardItem *headerItem = d->columnHeaderItems.at(column); + if (headerItem) { + headerItem->d_func()->setParentAndModel(0, 0); + d->columnHeaderItems.replace(column, 0); + } + return headerItem; +} + +/*! + \since 4.2 + + Removes the vertical header item at \a row from the header without + deleting it, and returns a pointer to the item. The model releases + ownership of the item. + + \sa verticalHeaderItem(), takeHorizontalHeaderItem() +*/ +QStandardItem *QStandardItemModel::takeVerticalHeaderItem(int row) +{ + Q_D(QStandardItemModel); + if ((row < 0) || (row >= rowCount())) + return 0; + QStandardItem *headerItem = d->rowHeaderItems.at(row); + if (headerItem) { + headerItem->d_func()->setParentAndModel(0, 0); + d->rowHeaderItems.replace(row, 0); + } + return headerItem; +} + +/*! + \since 4.2 + \property QStandardItemModel::sortRole + \brief the item role that is used to query the model's data when sorting items + + The default value is Qt::DisplayRole. + + \sa sort(), QStandardItem::sortChildren() +*/ +int QStandardItemModel::sortRole() const +{ + Q_D(const QStandardItemModel); + return d->sortRole; +} + +void QStandardItemModel::setSortRole(int role) +{ + Q_D(QStandardItemModel); + d->sortRole = role; +} + +/*! + \reimp +*/ +int QStandardItemModel::columnCount(const QModelIndex &parent) const +{ + Q_D(const QStandardItemModel); + QStandardItem *item = d->itemFromIndex(parent); + return item ? item->columnCount() : 0; +} + +/*! + \reimp +*/ +QVariant QStandardItemModel::data(const QModelIndex &index, int role) const +{ + Q_D(const QStandardItemModel); + QStandardItem *item = d->itemFromIndex(index); + return item ? item->data(role) : QVariant(); +} + +/*! + \reimp +*/ +Qt::ItemFlags QStandardItemModel::flags(const QModelIndex &index) const +{ + Q_D(const QStandardItemModel); + if (!d->indexValid(index)) + return d->root->flags(); + QStandardItem *item = d->itemFromIndex(index); + if (item) + return item->flags(); + return Qt::ItemIsSelectable + |Qt::ItemIsEnabled + |Qt::ItemIsEditable + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled; +} + +/*! + \reimp +*/ +bool QStandardItemModel::hasChildren(const QModelIndex &parent) const +{ + Q_D(const QStandardItemModel); + QStandardItem *item = d->itemFromIndex(parent); + return item ? item->hasChildren() : false; +} + +/*! + \reimp +*/ +QVariant QStandardItemModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_D(const QStandardItemModel); + if ((section < 0) + || ((orientation == Qt::Horizontal) && (section >= columnCount())) + || ((orientation == Qt::Vertical) && (section >= rowCount()))) { + return QVariant(); + } + QStandardItem *headerItem = 0; + if (orientation == Qt::Horizontal) + headerItem = d->columnHeaderItems.at(section); + else if (orientation == Qt::Vertical) + headerItem = d->rowHeaderItems.at(section); + return headerItem ? headerItem->data(role) + : QAbstractItemModel::headerData(section, orientation, role); +} + +/*! + \reimp + + QStandardItemModel supports both copy and move. +*/ +Qt::DropActions QStandardItemModel::supportedDropActions () const +{ + return Qt::CopyAction | Qt::MoveAction; +} + +/*! + \reimp +*/ +QModelIndex QStandardItemModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_D(const QStandardItemModel); + QStandardItem *parentItem = d->itemFromIndex(parent); + if ((parentItem == 0) + || (row < 0) + || (column < 0) + || (row >= parentItem->rowCount()) + || (column >= parentItem->columnCount())) { + return QModelIndex(); + } + return createIndex(row, column, parentItem); +} + +/*! + \reimp +*/ +bool QStandardItemModel::insertColumns(int column, int count, const QModelIndex &parent) +{ + Q_D(QStandardItemModel); + QStandardItem *item = parent.isValid() ? itemFromIndex(parent) : d->root; + if (item == 0) + return false; + return item->d_func()->insertColumns(column, count, QList<QStandardItem*>()); +} + +/*! + \reimp +*/ +bool QStandardItemModel::insertRows(int row, int count, const QModelIndex &parent) +{ + Q_D(QStandardItemModel); + QStandardItem *item = parent.isValid() ? itemFromIndex(parent) : d->root; + if (item == 0) + return false; + return item->d_func()->insertRows(row, count, QList<QStandardItem*>()); +} + +/*! + \reimp +*/ +QMap<int, QVariant> QStandardItemModel::itemData(const QModelIndex &index) const +{ + Q_D(const QStandardItemModel); + QStandardItem *item = d->itemFromIndex(index); + return item ? item->d_func()->itemData() : QMap<int, QVariant>(); +} + +/*! + \reimp +*/ +QModelIndex QStandardItemModel::parent(const QModelIndex &child) const +{ + Q_D(const QStandardItemModel); + if (!d->indexValid(child)) + return QModelIndex(); + QStandardItem *parentItem = static_cast<QStandardItem*>(child.internalPointer()); + return indexFromItem(parentItem); +} + +/*! + \reimp +*/ +bool QStandardItemModel::removeColumns(int column, int count, const QModelIndex &parent) +{ + Q_D(QStandardItemModel); + QStandardItem *item = d->itemFromIndex(parent); + if ((item == 0) || (count < 1) || (column < 0) || ((column + count) > item->columnCount())) + return false; + item->removeColumns(column, count); + return true; +} + +/*! + \reimp +*/ +bool QStandardItemModel::removeRows(int row, int count, const QModelIndex &parent) +{ + Q_D(QStandardItemModel); + QStandardItem *item = d->itemFromIndex(parent); + if ((item == 0) || (count < 1) || (row < 0) || ((row + count) > item->rowCount())) + return false; + item->removeRows(row, count); + return true; +} + +/*! + \reimp +*/ +int QStandardItemModel::rowCount(const QModelIndex &parent) const +{ + Q_D(const QStandardItemModel); + QStandardItem *item = d->itemFromIndex(parent); + return item ? item->rowCount() : 0; +} + +/*! + \reimp +*/ +bool QStandardItemModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid()) + return false; + QStandardItem *item = itemFromIndex(index); + if (item == 0) + return false; + item->setData(value, role); + return true; +} + +/*! + \reimp +*/ +bool QStandardItemModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) +{ + Q_D(QStandardItemModel); + if ((section < 0) + || ((orientation == Qt::Horizontal) && (section >= columnCount())) + || ((orientation == Qt::Vertical) && (section >= rowCount()))) { + return false; + } + QStandardItem *headerItem = 0; + if (orientation == Qt::Horizontal) { + headerItem = d->columnHeaderItems.at(section); + if (headerItem == 0) { + headerItem = d->createItem(); + headerItem->d_func()->setModel(this); + d->columnHeaderItems.replace(section, headerItem); + } + } else if (orientation == Qt::Vertical) { + headerItem = d->rowHeaderItems.at(section); + if (headerItem == 0) { + headerItem = d->createItem(); + headerItem->d_func()->setModel(this); + d->rowHeaderItems.replace(section, headerItem); + } + } + if (headerItem) { + headerItem->setData(value, role); + return true; + } + return false; +} + +/*! + \reimp +*/ +bool QStandardItemModel::setItemData(const QModelIndex &index, const QMap<int, QVariant> &roles) +{ + QStandardItem *item = itemFromIndex(index); + if (item == 0) + return false; + item->d_func()->setItemData(roles); + return true; +} + +/*! + \reimp +*/ +void QStandardItemModel::sort(int column, Qt::SortOrder order) +{ + Q_D(QStandardItemModel); + d->root->sortChildren(column, order); +} + +/*! + \fn QObject *QStandardItemModel::parent() const + \internal +*/ + + +/*! + \reimp +*/ +QStringList QStandardItemModel::mimeTypes() const +{ + return QAbstractItemModel::mimeTypes() << QLatin1String("application/x-qstandarditemmodeldatalist"); +} + +/*! + \reimp +*/ +QMimeData *QStandardItemModel::mimeData(const QModelIndexList &indexes) const +{ + QMimeData *data = QAbstractItemModel::mimeData(indexes); + if(!data) + return 0; + + QString format = QLatin1String("application/x-qstandarditemmodeldatalist"); + if (!mimeTypes().contains(format)) + return data; + QByteArray encoded; + QDataStream stream(&encoded, QIODevice::WriteOnly); + + QSet<QStandardItem*> itemsSet; + QStack<QStandardItem*> stack; + itemsSet.reserve(indexes.count()); + stack.reserve(indexes.count()); + foreach (const QModelIndex &index, indexes) { + QStandardItem *item = itemFromIndex(index); + itemsSet << item; + stack.push(item); + } + + //remove duplicates childrens + { + QSet<QStandardItem *> seen; + while (!stack.isEmpty()) { + QStandardItem *itm = stack.pop(); + if (seen.contains(itm)) + continue; + seen.insert(itm); + + const QVector<QStandardItem*> &childList = itm->d_func()->children; + for (int i = 0; i < childList.count(); ++i) { + QStandardItem *chi = childList.at(i); + if (chi) { + QSet<QStandardItem *>::iterator it = itemsSet.find(chi); + if (it != itemsSet.end()) { + itemsSet.erase(it); + } + stack.push(chi); + } + } + } + } + + stack.reserve(itemsSet.count()); + foreach (QStandardItem *item, itemsSet) { + stack.push(item); + } + + //stream everything recursively + while (!stack.isEmpty()) { + QStandardItem *item = stack.pop(); + if(itemsSet.contains(item)) { //if the item is selection 'top-level', strem its position + stream << item->row() << item->column(); + } + if(item) { + stream << *item << item->columnCount() << item->d_ptr->children.count(); + stack += item->d_ptr->children; + } else { + QStandardItem dummy; + stream << dummy << 0 << 0; + } + } + + data->setData(format, encoded); + return data; +} + + +/* \internal + Used by QStandardItemModel::dropMimeData + stream out an item and his children + */ +static void decodeDataRecursive(QDataStream &stream, QStandardItem *item) +{ + int colCount, childCount; + stream >> *item; + stream >> colCount >> childCount; + item->setColumnCount(colCount); + + int childPos = childCount; + + while(childPos > 0) { + childPos--; + QStandardItem *child = new QStandardItem; + decodeDataRecursive(stream, child); + item->setChild( childPos / colCount, childPos % colCount, child); + } +} + + +/*! + \reimp +*/ +bool QStandardItemModel::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent) +{ + // check if the action is supported + if (!data || !(action == Qt::CopyAction || action == Qt::MoveAction)) + return false; + // check if the format is supported + QString format = QLatin1String("application/x-qstandarditemmodeldatalist"); + if (!data->hasFormat(format)) + return QAbstractItemModel::dropMimeData(data, action, row, column, parent); + + if (row > rowCount(parent)) + row = rowCount(parent); + if (row == -1) + row = rowCount(parent); + if (column == -1) + column = 0; + + // decode and insert + QByteArray encoded = data->data(format); + QDataStream stream(&encoded, QIODevice::ReadOnly); + + + //code based on QAbstractItemModel::decodeData + // adapted to work with QStandardItem + int top = INT_MAX; + int left = INT_MAX; + int bottom = 0; + int right = 0; + QVector<int> rows, columns; + QVector<QStandardItem *> items; + + while (!stream.atEnd()) { + int r, c; + QStandardItem *item = new QStandardItem; + stream >> r >> c; + decodeDataRecursive(stream, item); + + rows.append(r); + columns.append(c); + items.append(item); + top = qMin(r, top); + left = qMin(c, left); + bottom = qMax(r, bottom); + right = qMax(c, right); + } + + // insert the dragged items into the table, use a bit array to avoid overwriting items, + // since items from different tables can have the same row and column + int dragRowCount = 0; + int dragColumnCount = right - left + 1; + + // Compute the number of continuous rows upon insertion and modify the rows to match + QVector<int> rowsToInsert(bottom + 1); + for (int i = 0; i < rows.count(); ++i) + rowsToInsert[rows.at(i)] = 1; + for (int i = 0; i < rowsToInsert.count(); ++i) { + if (rowsToInsert[i] == 1){ + rowsToInsert[i] = dragRowCount; + ++dragRowCount; + } + } + for (int i = 0; i < rows.count(); ++i) + rows[i] = top + rowsToInsert[rows[i]]; + + QBitArray isWrittenTo(dragRowCount * dragColumnCount); + + // make space in the table for the dropped data + int colCount = columnCount(parent); + if (colCount < dragColumnCount + column) { + insertColumns(colCount, dragColumnCount + column - colCount, parent); + colCount = columnCount(parent); + } + insertRows(row, dragRowCount, parent); + + row = qMax(0, row); + column = qMax(0, column); + + QStandardItem *parentItem = itemFromIndex (parent); + if (!parentItem) + parentItem = invisibleRootItem(); + + QVector<QPersistentModelIndex> newIndexes(items.size()); + // set the data in the table + for (int j = 0; j < items.size(); ++j) { + int relativeRow = rows.at(j) - top; + int relativeColumn = columns.at(j) - left; + int destinationRow = relativeRow + row; + int destinationColumn = relativeColumn + column; + int flat = (relativeRow * dragColumnCount) + relativeColumn; + // if the item was already written to, or we just can't fit it in the table, create a new row + if (destinationColumn >= colCount || isWrittenTo.testBit(flat)) { + destinationColumn = qBound(column, destinationColumn, colCount - 1); + destinationRow = row + dragRowCount; + insertRows(row + dragRowCount, 1, parent); + flat = (dragRowCount * dragColumnCount) + relativeColumn; + isWrittenTo.resize(++dragRowCount * dragColumnCount); + } + if (!isWrittenTo.testBit(flat)) { + newIndexes[j] = index(destinationRow, destinationColumn, parentItem->index()); + isWrittenTo.setBit(flat); + } + } + + for(int k = 0; k < newIndexes.size(); k++) { + if (newIndexes.at(k).isValid()) { + parentItem->setChild(newIndexes.at(k).row(), newIndexes.at(k).column(), items.at(k)); + } else { + delete items.at(k); + } + } + + return true; +} + +QT_END_NAMESPACE + +#include "moc_qstandarditemmodel.cpp" + +#endif // QT_NO_STANDARDITEMMODEL diff --git a/src/gui/itemviews/qstandarditemmodel.h b/src/gui/itemviews/qstandarditemmodel.h new file mode 100644 index 0000000..f24f0ab --- /dev/null +++ b/src/gui/itemviews/qstandarditemmodel.h @@ -0,0 +1,456 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTANDARDITEMMODEL_H +#define QSTANDARDITEMMODEL_H + +#include <QtCore/qabstractitemmodel.h> +#include <QtGui/qbrush.h> +#include <QtGui/qfont.h> +#include <QtGui/qicon.h> +#ifndef QT_NO_DATASTREAM +#include <QtCore/qdatastream.h> +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_STANDARDITEMMODEL + +template <class T> class QList; + +class QStandardItemModel; + +class QStandardItemPrivate; +class Q_GUI_EXPORT QStandardItem +{ +public: + QStandardItem(); + QStandardItem(const QString &text); + QStandardItem(const QIcon &icon, const QString &text); + explicit QStandardItem(int rows, int columns = 1); + virtual ~QStandardItem(); + + virtual QVariant data(int role = Qt::UserRole + 1) const; + virtual void setData(const QVariant &value, int role = Qt::UserRole + 1); + + inline QString text() const { + return qvariant_cast<QString>(data(Qt::DisplayRole)); + } + inline void setText(const QString &text); + + inline QIcon icon() const { + return qvariant_cast<QIcon>(data(Qt::DecorationRole)); + } + inline void setIcon(const QIcon &icon); + +#ifndef QT_NO_TOOLTIP + inline QString toolTip() const { + return qvariant_cast<QString>(data(Qt::ToolTipRole)); + } + inline void setToolTip(const QString &toolTip); +#endif + +#ifndef QT_NO_STATUSTIP + inline QString statusTip() const { + return qvariant_cast<QString>(data(Qt::StatusTipRole)); + } + inline void setStatusTip(const QString &statusTip); +#endif + +#ifndef QT_NO_WHATSTHIS + inline QString whatsThis() const { + return qvariant_cast<QString>(data(Qt::WhatsThisRole)); + } + inline void setWhatsThis(const QString &whatsThis); +#endif + + inline QSize sizeHint() const { + return qvariant_cast<QSize>(data(Qt::SizeHintRole)); + } + inline void setSizeHint(const QSize &sizeHint); + + inline QFont font() const { + return qvariant_cast<QFont>(data(Qt::FontRole)); + } + inline void setFont(const QFont &font); + + inline Qt::Alignment textAlignment() const { + return Qt::Alignment(qvariant_cast<int>(data(Qt::TextAlignmentRole))); + } + inline void setTextAlignment(Qt::Alignment textAlignment); + + inline QBrush background() const { + return qvariant_cast<QBrush>(data(Qt::BackgroundRole)); + } + inline void setBackground(const QBrush &brush); + + inline QBrush foreground() const { + return qvariant_cast<QBrush>(data(Qt::ForegroundRole)); + } + inline void setForeground(const QBrush &brush); + + inline Qt::CheckState checkState() const { + return Qt::CheckState(qvariant_cast<int>(data(Qt::CheckStateRole))); + } + inline void setCheckState(Qt::CheckState checkState); + + inline QString accessibleText() const { + return qvariant_cast<QString>(data(Qt::AccessibleTextRole)); + } + inline void setAccessibleText(const QString &accessibleText); + + inline QString accessibleDescription() const { + return qvariant_cast<QString>(data(Qt::AccessibleDescriptionRole)); + } + inline void setAccessibleDescription(const QString &accessibleDescription); + + Qt::ItemFlags flags() const; + void setFlags(Qt::ItemFlags flags); + + inline bool isEnabled() const { + return (flags() & Qt::ItemIsEnabled) != 0; + } + void setEnabled(bool enabled); + + inline bool isEditable() const { + return (flags() & Qt::ItemIsEditable) != 0; + } + void setEditable(bool editable); + + inline bool isSelectable() const { + return (flags() & Qt::ItemIsSelectable) != 0; + } + void setSelectable(bool selectable); + + inline bool isCheckable() const { + return (flags() & Qt::ItemIsUserCheckable) != 0; + } + void setCheckable(bool checkable); + + inline bool isTristate() const { + return (flags() & Qt::ItemIsTristate) != 0; + } + void setTristate(bool tristate); + +#ifndef QT_NO_DRAGANDDROP + inline bool isDragEnabled() const { + return (flags() & Qt::ItemIsDragEnabled) != 0; + } + void setDragEnabled(bool dragEnabled); + + inline bool isDropEnabled() const { + return (flags() & Qt::ItemIsDropEnabled) != 0; + } + void setDropEnabled(bool dropEnabled); +#endif // QT_NO_DRAGANDDROP + + QStandardItem *parent() const; + int row() const; + int column() const; + QModelIndex index() const; + QStandardItemModel *model() const; + + int rowCount() const; + void setRowCount(int rows); + int columnCount() const; + void setColumnCount(int columns); + + bool hasChildren() const; + QStandardItem *child(int row, int column = 0) const; + void setChild(int row, int column, QStandardItem *item); + inline void setChild(int row, QStandardItem *item); + + void insertRow(int row, const QList<QStandardItem*> &items); + void insertColumn(int column, const QList<QStandardItem*> &items); + void insertRows(int row, const QList<QStandardItem*> &items); + void insertRows(int row, int count); + void insertColumns(int column, int count); + + void removeRow(int row); + void removeColumn(int column); + void removeRows(int row, int count); + void removeColumns(int column, int count); + + inline void appendRow(const QList<QStandardItem*> &items); + inline void appendRows(const QList<QStandardItem*> &items); + inline void appendColumn(const QList<QStandardItem*> &items); + inline void insertRow(int row, QStandardItem *item); + inline void appendRow(QStandardItem *item); + + QStandardItem *takeChild(int row, int column = 0); + QList<QStandardItem*> takeRow(int row); + QList<QStandardItem*> takeColumn(int column); + + void sortChildren(int column, Qt::SortOrder order = Qt::AscendingOrder); + + virtual QStandardItem *clone() const; + + enum ItemType { Type = 0, UserType = 1000 }; + virtual int type() const; + +#ifndef QT_NO_DATASTREAM + virtual void read(QDataStream &in); + virtual void write(QDataStream &out) const; +#endif + virtual bool operator<(const QStandardItem &other) const; + +protected: + QStandardItem(const QStandardItem &other); + QStandardItem(QStandardItemPrivate &dd); + QStandardItem &operator=(const QStandardItem &other); + QStandardItemPrivate *d_ptr; + + void emitDataChanged(); + +private: + Q_DECLARE_PRIVATE(QStandardItem) + friend class QStandardItemModelPrivate; + friend class QStandardItemModel; +}; + +inline void QStandardItem::setText(const QString &atext) +{ setData(atext, Qt::DisplayRole); } + +inline void QStandardItem::setIcon(const QIcon &aicon) +{ setData(aicon, Qt::DecorationRole); } + +#ifndef QT_NO_TOOLTIP +inline void QStandardItem::setToolTip(const QString &atoolTip) +{ setData(atoolTip, Qt::ToolTipRole); } +#endif + +#ifndef QT_NO_STATUSTIP +inline void QStandardItem::setStatusTip(const QString &astatusTip) +{ setData(astatusTip, Qt::StatusTipRole); } +#endif + +#ifndef QT_NO_WHATSTHIS +inline void QStandardItem::setWhatsThis(const QString &awhatsThis) +{ setData(awhatsThis, Qt::WhatsThisRole); } +#endif + +inline void QStandardItem::setSizeHint(const QSize &asizeHint) +{ setData(asizeHint, Qt::SizeHintRole); } + +inline void QStandardItem::setFont(const QFont &afont) +{ setData(afont, Qt::FontRole); } + +inline void QStandardItem::setTextAlignment(Qt::Alignment atextAlignment) +{ setData(int(atextAlignment), Qt::TextAlignmentRole); } + +inline void QStandardItem::setBackground(const QBrush &abrush) +{ setData(abrush, Qt::BackgroundRole); } + +inline void QStandardItem::setForeground(const QBrush &abrush) +{ setData(abrush, Qt::ForegroundRole); } + +inline void QStandardItem::setCheckState(Qt::CheckState acheckState) +{ setData(acheckState, Qt::CheckStateRole); } + +inline void QStandardItem::setAccessibleText(const QString &aaccessibleText) +{ setData(aaccessibleText, Qt::AccessibleTextRole); } + +inline void QStandardItem::setAccessibleDescription(const QString &aaccessibleDescription) +{ setData(aaccessibleDescription, Qt::AccessibleDescriptionRole); } + +inline void QStandardItem::setChild(int arow, QStandardItem *aitem) +{ setChild(arow, 0, aitem); } + +inline void QStandardItem::appendRow(const QList<QStandardItem*> &aitems) +{ insertRow(rowCount(), aitems); } + +inline void QStandardItem::appendRows(const QList<QStandardItem*> &aitems) +{ insertRows(rowCount(), aitems); } + +inline void QStandardItem::appendColumn(const QList<QStandardItem*> &aitems) +{ insertColumn(columnCount(), aitems); } + +inline void QStandardItem::insertRow(int arow, QStandardItem *aitem) +{ insertRow(arow, QList<QStandardItem*>() << aitem); } + +inline void QStandardItem::appendRow(QStandardItem *aitem) +{ insertRow(rowCount(), aitem); } + +class QStandardItemModelPrivate; + +class Q_GUI_EXPORT QStandardItemModel : public QAbstractItemModel +{ + Q_OBJECT + Q_PROPERTY(int sortRole READ sortRole WRITE setSortRole) + +public: + explicit QStandardItemModel(QObject *parent = 0); + QStandardItemModel(int rows, int columns, QObject *parent = 0); + ~QStandardItemModel(); + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &child) const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + bool hasChildren(const QModelIndex &parent = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, + int role = Qt::EditRole); + + bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()); + bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex()); + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex()); + + Qt::ItemFlags flags(const QModelIndex &index) const; + Qt::DropActions supportedDropActions() const; + + QMap<int, QVariant> itemData(const QModelIndex &index) const; + bool setItemData(const QModelIndex &index, const QMap<int, QVariant> &roles); + + void clear(); + +#ifdef Q_NO_USING_KEYWORD + inline QObject *parent() const { return QObject::parent(); } +#else + using QObject::parent; +#endif + + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + + QStandardItem *itemFromIndex(const QModelIndex &index) const; + QModelIndex indexFromItem(const QStandardItem *item) const; + + QStandardItem *item(int row, int column = 0) const; + void setItem(int row, int column, QStandardItem *item); + inline void setItem(int row, QStandardItem *item); + QStandardItem *invisibleRootItem() const; + + QStandardItem *horizontalHeaderItem(int column) const; + void setHorizontalHeaderItem(int column, QStandardItem *item); + QStandardItem *verticalHeaderItem(int row) const; + void setVerticalHeaderItem(int row, QStandardItem *item); + + void setHorizontalHeaderLabels(const QStringList &labels); + void setVerticalHeaderLabels(const QStringList &labels); + + void setRowCount(int rows); + void setColumnCount(int columns); + + void appendRow(const QList<QStandardItem*> &items); + void appendColumn(const QList<QStandardItem*> &items); + inline void appendRow(QStandardItem *item); + + void insertRow(int row, const QList<QStandardItem*> &items); + void insertColumn(int column, const QList<QStandardItem*> &items); + inline void insertRow(int row, QStandardItem *item); + + inline bool insertRow(int row, const QModelIndex &parent = QModelIndex()); + inline bool insertColumn(int column, const QModelIndex &parent = QModelIndex()); + + QStandardItem *takeItem(int row, int column = 0); + QList<QStandardItem*> takeRow(int row); + QList<QStandardItem*> takeColumn(int column); + + QStandardItem *takeHorizontalHeaderItem(int column); + QStandardItem *takeVerticalHeaderItem(int row); + + const QStandardItem *itemPrototype() const; + void setItemPrototype(const QStandardItem *item); + + QList<QStandardItem*> findItems(const QString &text, + Qt::MatchFlags flags = Qt::MatchExactly, + int column = 0) const; + + int sortRole() const; + void setSortRole(int role); + + QStringList mimeTypes() const; + QMimeData *mimeData(const QModelIndexList &indexes) const; + bool dropMimeData (const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); + +Q_SIGNALS: + void itemChanged(QStandardItem *item); + +protected: + QStandardItemModel(QStandardItemModelPrivate &dd, QObject *parent = 0); + +private: + friend class QStandardItemPrivate; + friend class QStandardItem; + Q_DISABLE_COPY(QStandardItemModel) + Q_DECLARE_PRIVATE(QStandardItemModel) + + Q_PRIVATE_SLOT(d_func(), void _q_emitItemChanged(const QModelIndex &topLeft, + const QModelIndex &bottomRight)) +}; + +inline void QStandardItemModel::setItem(int arow, QStandardItem *aitem) +{ setItem(arow, 0, aitem); } + +inline void QStandardItemModel::appendRow(QStandardItem *aitem) +{ appendRow(QList<QStandardItem*>() << aitem); } + +inline void QStandardItemModel::insertRow(int arow, QStandardItem *aitem) +{ insertRow(arow, QList<QStandardItem*>() << aitem); } + +inline bool QStandardItemModel::insertRow(int arow, const QModelIndex &aparent) +{ return QAbstractItemModel::insertRow(arow, aparent); } +inline bool QStandardItemModel::insertColumn(int acolumn, const QModelIndex &aparent) +{ return QAbstractItemModel::insertColumn(acolumn, aparent); } + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &in, QStandardItem &item); +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &out, const QStandardItem &item); +#endif + +#endif // QT_NO_STANDARDITEMMODEL + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif //QSTANDARDITEMMODEL_H diff --git a/src/gui/itemviews/qstandarditemmodel_p.h b/src/gui/itemviews/qstandarditemmodel_p.h new file mode 100644 index 0000000..721194f --- /dev/null +++ b/src/gui/itemviews/qstandarditemmodel_p.h @@ -0,0 +1,189 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTANDARDITEMMODEL_P_H +#define QSTANDARDITEMMODEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qabstractitemmodel_p.h" + +#ifndef QT_NO_STANDARDITEMMODEL + +#include <private/qwidgetitemdata_p.h> +#include <QtCore/qlist.h> +#include <QtCore/qpair.h> +#include <QtCore/qstack.h> +#include <QtCore/qvariant.h> +#include <QtCore/qvector.h> + +QT_BEGIN_NAMESPACE + +class QStandardItemPrivate +{ + Q_DECLARE_PUBLIC(QStandardItem) +public: + inline QStandardItemPrivate() + : model(0), + parent(0), + rows(0), + columns(0), + lastIndexOf(2) + { } + virtual ~QStandardItemPrivate(); + + inline int childIndex(int row, int column) const { + if ((row < 0) || (column < 0) + || (row >= rowCount()) || (column >= columnCount())) { + return -1; + } + return (row * columnCount()) + column; + } + inline int childIndex(const QStandardItem *child) { + int start = qMax(0, lastIndexOf -2); + lastIndexOf = children.indexOf(const_cast<QStandardItem*>(child), start); + if (lastIndexOf == -1 && start != 0) + lastIndexOf = children.lastIndexOf(const_cast<QStandardItem*>(child), start); + return lastIndexOf; + } + QPair<int, int> position() const; + void setChild(int row, int column, QStandardItem *item, + bool emitChanged = false); + inline int rowCount() const { + return rows; + } + inline int columnCount() const { + return columns; + } + void childDeleted(QStandardItem *child); + + void setModel(QStandardItemModel *mod); + + inline void setParentAndModel( + QStandardItem *par, + QStandardItemModel *mod) { + setModel(mod); + parent = par; + } + + void changeFlags(bool enable, Qt::ItemFlags f); + void setItemData(const QMap<int, QVariant> &roles); + const QMap<int, QVariant> itemData() const; + + bool insertRows(int row, int count, const QList<QStandardItem*> &items); + bool insertRows(int row, const QList<QStandardItem*> &items); + bool insertColumns(int column, int count, const QList<QStandardItem*> &items); + + void sortChildren(int column, Qt::SortOrder order); + + QStandardItemModel *model; + QStandardItem *parent; + QVector<QWidgetItemData> values; + QVector<QStandardItem*> children; + int rows; + int columns; + + QStandardItem *q_ptr; + + int lastIndexOf; +}; + +class QStandardItemModelPrivate : public QAbstractItemModelPrivate +{ + Q_DECLARE_PUBLIC(QStandardItemModel) + +public: + QStandardItemModelPrivate(); + virtual ~QStandardItemModelPrivate(); + + void init(); + + inline QStandardItem *createItem() const { + return itemPrototype ? itemPrototype->clone() : new QStandardItem; + } + + inline QStandardItem *itemFromIndex(const QModelIndex &index) const { + Q_Q(const QStandardItemModel); + if (!index.isValid()) + return root; + if (index.model() != q) + return 0; + QStandardItem *parent = static_cast<QStandardItem*>(index.internalPointer()); + if (parent == 0) + return 0; + return parent->child(index.row(), index.column()); + } + + void sort(QStandardItem *parent, int column, Qt::SortOrder order); + void itemChanged(QStandardItem *item); + void rowsAboutToBeInserted(QStandardItem *parent, int start, int end); + void columnsAboutToBeInserted(QStandardItem *parent, int start, int end); + void rowsAboutToBeRemoved(QStandardItem *parent, int start, int end); + void columnsAboutToBeRemoved(QStandardItem *parent, int start, int end); + void rowsInserted(QStandardItem *parent, int row, int count); + void columnsInserted(QStandardItem *parent, int column, int count); + void rowsRemoved(QStandardItem *parent, int row, int count); + void columnsRemoved(QStandardItem *parent, int column, int count); + + void _q_emitItemChanged(const QModelIndex &topLeft, + const QModelIndex &bottomRight); + + QVector<QStandardItem*> columnHeaderItems; + QVector<QStandardItem*> rowHeaderItems; + QStandardItem *root; + const QStandardItem *itemPrototype; + int sortRole; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_STANDARDITEMMODEL + +#endif // QSTANDARDITEMMODEL_P_H diff --git a/src/gui/itemviews/qstringlistmodel.cpp b/src/gui/itemviews/qstringlistmodel.cpp new file mode 100644 index 0000000..6d20907 --- /dev/null +++ b/src/gui/itemviews/qstringlistmodel.cpp @@ -0,0 +1,307 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + A simple model that uses a QStringList as its data source. +*/ + +#include "qstringlistmodel.h" + +#ifndef QT_NO_STRINGLISTMODEL + +QT_BEGIN_NAMESPACE + +/*! + \class QStringListModel + \brief The QStringListModel class provides a model that supplies strings to views. + + \ingroup model-view + \mainclass + + QStringListModel is an editable model that can be used for simple + cases where you need to display a number of strings in a view + widget, such as a QListView or a QComboBox. + + The model provides all the standard functions of an editable + model, representing the data in the string list as a model with + one column and a number of rows equal to the number of items in + the list. + + Model indexes corresponding to items are obtained with the + \l{QAbstractListModel::index()}{index()} function, and item flags + are obtained with flags(). Item data is read with the data() + function and written with setData(). The number of rows (and + number of items in the string list) can be found with the + rowCount() function. + + The model can be constructed with an existing string list, or + strings can be set later with the setStringList() convenience + function. Strings can also be inserted in the usual way with the + insertRows() function, and removed with removeRows(). The contents + of the string list can be retrieved with the stringList() + convenience function. + + An example usage of QStringListModel: + + \snippet doc/src/snippets/qstringlistmodel/main.cpp 0 + + \sa QAbstractListModel, QAbstractItemModel, {Model Classes} +*/ + +/*! + Constructs a string list model with the given \a parent. +*/ + +QStringListModel::QStringListModel(QObject *parent) + : QAbstractListModel(parent) +{ +} + +/*! + Constructs a string list model containing the specified \a strings + with the given \a parent. +*/ + +QStringListModel::QStringListModel(const QStringList &strings, QObject *parent) + : QAbstractListModel(parent), lst(strings) +{ +} + +/*! + Returns the number of rows in the model. This value corresponds to the + number of items in the model's internal string list. + + The optional \a parent argument is in most models used to specify + the parent of the rows to be counted. Because this is a list if a + valid parent is specified, the result will always be 0. + + \sa insertRows(), removeRows(), QAbstractItemModel::rowCount() +*/ + +int QStringListModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + return lst.count(); +} + +/*! + Returns data for the specified \a role, from the item with the + given \a index. + + If the view requests an invalid index, an invalid variant is returned. + + \sa setData() +*/ + +QVariant QStringListModel::data(const QModelIndex &index, int role) const +{ + if (index.row() < 0 || index.row() >= lst.size()) + return QVariant(); + + if (role == Qt::DisplayRole || role == Qt::EditRole) + return lst.at(index.row()); + + return QVariant(); +} + +/*! + Returns the flags for the item with the given \a index. + + Valid items are enabled, selectable, editable, drag enabled and drop enabled. + + \sa QAbstractItemModel::flags() +*/ + +Qt::ItemFlags QStringListModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return QAbstractItemModel::flags(index) | Qt::ItemIsDropEnabled; + + return QAbstractItemModel::flags(index) | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; +} + +/*! + Sets the data for the specified \a role in the item with the given + \a index in the model, to the provided \a value. + + The dataChanged() signal is emitted if the item is changed. + + \sa Qt::ItemDataRole, data() +*/ + +bool QStringListModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (index.row() >= 0 && index.row() < lst.size() + && (role == Qt::EditRole || role == Qt::DisplayRole)) { + lst.replace(index.row(), value.toString()); + emit dataChanged(index, index); + return true; + } + return false; +} + +/*! + Inserts \a count rows into the model, beginning at the given \a row. + + The \a parent index of the rows is optional and is only used for + consistency with QAbstractItemModel. By default, a null index is + specified, indicating that the rows are inserted in the top level of + the model. + + \sa QAbstractItemModel::insertRows() +*/ + +bool QStringListModel::insertRows(int row, int count, const QModelIndex &parent) +{ + if (count < 1 || row < 0 || row > rowCount(parent)) + return false; + + beginInsertRows(QModelIndex(), row, row + count - 1); + + for (int r = 0; r < count; ++r) + lst.insert(row, QString()); + + endInsertRows(); + + return true; +} + +/*! + Removes \a count rows from the model, beginning at the given \a row. + + The \a parent index of the rows is optional and is only used for + consistency with QAbstractItemModel. By default, a null index is + specified, indicating that the rows are removed in the top level of + the model. + + \sa QAbstractItemModel::removeRows() +*/ + +bool QStringListModel::removeRows(int row, int count, const QModelIndex &parent) +{ + if (count <= 0 || row < 0 || (row + count) > rowCount(parent)) + return false; + + beginRemoveRows(QModelIndex(), row, row + count - 1); + + for (int r = 0; r < count; ++r) + lst.removeAt(row); + + endRemoveRows(); + + return true; +} + +static bool ascendingLessThan(const QPair<QString, int> &s1, const QPair<QString, int> &s2) +{ + return s1.first < s2.first; +} + +static bool decendingLessThan(const QPair<QString, int> &s1, const QPair<QString, int> &s2) +{ + return s1.first > s2.first; +} + +/*! + \reimp +*/ +void QStringListModel::sort(int, Qt::SortOrder order) +{ + emit layoutAboutToBeChanged(); + + QList<QPair<QString, int> > list; + for (int i = 0; i < lst.count(); ++i) + list.append(QPair<QString, int>(lst.at(i), i)); + + if (order == Qt::AscendingOrder) + qSort(list.begin(), list.end(), ascendingLessThan); + else + qSort(list.begin(), list.end(), decendingLessThan); + + lst.clear(); + QVector<int> forwarding(list.count()); + for (int i = 0; i < list.count(); ++i) { + lst.append(list.at(i).first); + forwarding[list.at(i).second] = i; + } + + QModelIndexList oldList = persistentIndexList(); + QModelIndexList newList; + for (int i = 0; i < oldList.count(); ++i) + newList.append(index(forwarding.at(oldList.at(i).row()), 0)); + changePersistentIndexList(oldList, newList); + + emit layoutChanged(); +} + +/*! + Returns the string list used by the model to store data. +*/ +QStringList QStringListModel::stringList() const +{ + return lst; +} + +/*! + Sets the model's internal string list to \a strings. The model will + notify any attached views that its underlying data has changed. + + \sa dataChanged() +*/ +void QStringListModel::setStringList(const QStringList &strings) +{ + lst = strings; + reset(); +} + +/*! + \reimp +*/ +Qt::DropActions QStringListModel::supportedDropActions() const +{ + return QAbstractItemModel::supportedDropActions() | Qt::MoveAction; +} + +QT_END_NAMESPACE + +#endif // QT_NO_STRINGLISTMODEL diff --git a/src/gui/itemviews/qstringlistmodel.h b/src/gui/itemviews/qstringlistmodel.h new file mode 100644 index 0000000..d2d0f70 --- /dev/null +++ b/src/gui/itemviews/qstringlistmodel.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTRINGLISTMODEL_H +#define QSTRINGLISTMODEL_H + +#include <QtCore/qstringlist.h> +#include <QtGui/qabstractitemview.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_STRINGLISTMODEL + +class Q_GUI_EXPORT QStringListModel : public QAbstractListModel +{ + Q_OBJECT +public: + explicit QStringListModel(QObject *parent = 0); + QStringListModel(const QStringList &strings, QObject *parent = 0); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + Qt::ItemFlags flags(const QModelIndex &index) const; + + bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()); + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + + QStringList stringList() const; + void setStringList(const QStringList &strings); + + Qt::DropActions supportedDropActions() const; + +private: + Q_DISABLE_COPY(QStringListModel) + QStringList lst; +}; + +#endif // QT_NO_STRINGLISTMODEL + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSTRINGLISTMODEL_H diff --git a/src/gui/itemviews/qstyleditemdelegate.cpp b/src/gui/itemviews/qstyleditemdelegate.cpp new file mode 100644 index 0000000..271cc0d --- /dev/null +++ b/src/gui/itemviews/qstyleditemdelegate.cpp @@ -0,0 +1,762 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qstyleditemdelegate.h" + +#ifndef QT_NO_ITEMVIEWS +#include <qabstractitemmodel.h> +#include <qapplication.h> +#include <qbrush.h> +#include <qlineedit.h> +#include <qtextedit.h> +#include <qpainter.h> +#include <qpalette.h> +#include <qpoint.h> +#include <qrect.h> +#include <qsize.h> +#include <qstyle.h> +#include <qdatetime.h> +#include <qstyleoption.h> +#include <qevent.h> +#include <qpixmap.h> +#include <qbitmap.h> +#include <qpixmapcache.h> +#include <qitemeditorfactory.h> +#include <private/qitemeditorfactory_p.h> +#include <qmetaobject.h> +#include <qtextlayout.h> +#include <private/qobject_p.h> +#include <private/qdnd_p.h> +#include <private/qtextengine_p.h> +#include <private/qlayoutengine_p.h> +#include <qdebug.h> +#include <qlocale.h> +#include <qdialog.h> +#include <qtableview.h> + +#include <limits.h> + +QT_BEGIN_NAMESPACE + +class QStyledItemDelegatePrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QStyledItemDelegate) + +public: + QStyledItemDelegatePrivate() : factory(0) { } + + static const QWidget *widget(const QStyleOptionViewItem &option) + { + if (const QStyleOptionViewItemV3 *v3 = qstyleoption_cast<const QStyleOptionViewItemV3 *>(&option)) + return v3->widget; + return 0; + } + + const QItemEditorFactory *editorFactory() const + { + return factory ? factory : QItemEditorFactory::defaultFactory(); + } + + void _q_commitDataAndCloseEditor(QWidget *editor) + { + Q_Q(QStyledItemDelegate); + emit q->commitData(editor); + emit q->closeEditor(editor, QAbstractItemDelegate::SubmitModelCache); + } + QItemEditorFactory *factory; +}; + +/*! + \class QStyledItemDelegate + + \brief The QStyledItemDelegate class provides display and editing facilities for + data items from a model. + + \ingroup model-view + \mainclass + \since 4.4 + + When displaying data from models in Qt item views, e.g., a + QTableView, the individual items are drawn by a delegate. Also, + when an item is edited, it provides an editor widget, which is + placed on top of the item view while editing takes place. + QStyledItemDelegate is the default delegate for all Qt item + views, and is installed upon them when they are created. + + The QStyledItemDelegate class is one of the \l{Model/View Classes} + and is part of Qt's \l{Model/View Programming}{model/view + framework}. The delegate allows the display and editing of items + to be developed independently from the model and view. + + The data of items in models are assigned an + \l{Qt::}{ItemDataRole}; each item can store a QVariant for each + role. QStyledItemDelegate implements display and editing for the + most common datatypes expected by users, including booleans, + integers, and strings. + + The data will be drawn differently depending on which role they + have in the model. The following table describes the roles and the + data types the delegate can handle for each of them. It is often + sufficient to ensure that the model returns appropriate data for + each of the roles to determine the appearance of items in views. + + \table + \header \o Role \o Accepted Types + \omit + \row \o \l Qt::AccessibleDescriptionRole \o QString + \row \o \l Qt::AccessibleTextRole \o QString + \endomit + \row \o \l Qt::BackgroundRole \o QBrush + \row \o \l Qt::BackgroundColorRole \o QColor (obsolete; use Qt::BackgroundRole instead) + \row \o \l Qt::CheckStateRole \o Qt::CheckState + \row \o \l Qt::DecorationRole \o QIcon and QColor + \row \o \l Qt::DisplayRole \o QString and types with a string representation + \row \o \l Qt::EditRole \o See QItemEditorFactory for details + \row \o \l Qt::FontRole \o QFont + \row \o \l Qt::SizeHintRole \o QSize + \omit + \row \o \l Qt::StatusTipRole \o + \endomit + \row \o \l Qt::TextAlignmentRole \o Qt::Alignment + \row \o \l Qt::ForegroundRole \o QBrush + \row \o \l Qt::TextColorRole \o QColor (obsolete; use Qt::ForegroundRole instead) + \omit + \row \o \l Qt::ToolTipRole + \row \o \l Qt::WhatsThisRole + \endomit + \endtable + + Editors are created with a QItemEditorFactory; a default static + instance provided by QItemEditorFactory is installed on all item + delegates. You can set a custom factory using + setItemEditorFactory() or set a new default factory with + QItemEditorFactory::setDefaultFactory(). It is the data stored in + the item model with the \l{Qt::}{EditRole} that is edited. See the + QItemEditorFactory class for a more high-level introduction to + item editor factories. The \l{Color Editor Factory Example}{Color + Editor Factory} example shows how to create custom editors with a + factory. + + \section1 Subclassing QStyledItemDelegate + + If the delegate does not support painting of the data types you + need or you want to customize the drawing of items, you need to + subclass QStyledItemDelegate, and reimplement paint() and possibly + sizeHint(). The paint() function is called individually for each + item, and with sizeHint(), you can specify the hint for each + of them. + + When reimplementing paint(), one would typically handle the + datatypes one would like to draw and use the superclass + implementation for other types. + + The painting of check box indicators are performed by the current + style. The style also specifies the size and the bounding + rectangles in which to draw the data for the different data roles. + The bounding rectangle of the item itself is also calculated by + the style. When drawing already supported datatypes, it is + therefore a good idea to ask the style for these bounding + rectangles. The QStyle class description describes this in + more detail. + + If you wish to change any of the bounding rectangles calculated by + the style or the painting of check box indicators, you can + subclass QStyle. Note, however, that the size of the items can + also be affected by reimplementing sizeHint(). + + It is possible for a custom delegate to provide editors + without the use of an editor item factory. In this case, the + following virtual functions must be reimplemented: + + \list + \o createEditor() returns the widget used to change data from the model + and can be reimplemented to customize editing behavior. + \o setEditorData() provides the widget with data to manipulate. + \o updateEditorGeometry() ensures that the editor is displayed correctly + with respect to the item view. + \o setModelData() returns updated data to the model. + \endlist + + The \l{Star Delegate Example}{Star Delegate} example creates + editors by reimplementing these methods. + + \section1 QStyledItemDelegate and QItemDelegate + + QStyledItemDelegate has taken over the job as default delegate - + leaving QItemDelegate behind. They will now co-exist peacefully as + independent alternatives to painting and providing editors for + items in views. The difference between them is that the new + delegate uses the current style to paint its items. We therefore + recommend using QStyledItemDelegate as base when implementing + custom delegates. The code required should be equal unless the + custom delegate also wishes to use the style for drawing. + + If you wish to customize the painting of item views, you should + implement a custom style. Please see the QStyle class + documentation for details. + + \sa {Delegate Classes}, QItemDelegate, QAbstractItemDelegate, QStyle, + {Spin Box Delegate Example}, {Star Delegate Example}, {Color + Editor Factory Example} +*/ + + +/*! + Constructs an item delegate with the given \a parent. +*/ +QStyledItemDelegate::QStyledItemDelegate(QObject *parent) + : QAbstractItemDelegate(*new QStyledItemDelegatePrivate(), parent) +{ +} + +/*! + Destroys the item delegate. +*/ +QStyledItemDelegate::~QStyledItemDelegate() +{ +} + +/*! + This function returns the string that the delegate will use to display the + Qt::DisplayRole of the model in \a locale. \a value is the value of the Qt::DisplayRole + provided by the model. + + The default implementation uses the QLocale::toString to convert \a value into + a QString. +*/ +QString QStyledItemDelegate::displayText(const QVariant &value, const QLocale& locale) const +{ + QString text; + switch (value.type()) { + case QVariant::Double: + text = locale.toString(value.toDouble()); + break; + case QVariant::Int: + case QVariant::LongLong: + text = locale.toString(value.toLongLong()); + break; + case QVariant::UInt: + case QVariant::ULongLong: + text = locale.toString(value.toULongLong()); + break; + case QVariant::Date: + text = locale.toString(value.toDate(), QLocale::ShortFormat); + break; + case QVariant::Time: + text = locale.toString(value.toTime(), QLocale::ShortFormat); + break; + case QVariant::DateTime: + text = locale.toString(value.toDateTime().date(), QLocale::ShortFormat); + text += QLatin1Char(' '); + text += locale.toString(value.toDateTime().time(), QLocale::ShortFormat); + break; + default: + // convert new lines into line separators + text = value.toString(); + for (int i = 0; i < text.count(); ++i) { + if (text.at(i) == QLatin1Char('\n')) + text[i] = QChar::LineSeparator; + } + break; + } + return text; +} + +/*! + Initialize \a option with the values using the index \a index. This method + is useful for subclasses when they need a QStyleOptionViewItem, but don't want + to fill in all the information themselves. This function will check the version + of the QStyleOptionViewItem and fill in the additional values for a + QStyleOptionViewItemV2, QStyleOptionViewItemV3 and QStyleOptionViewItemV4. + + \sa QStyleOption::initFrom() +*/ +void QStyledItemDelegate::initStyleOption(QStyleOptionViewItem *option, + const QModelIndex &index) const +{ + QVariant value = index.data(Qt::FontRole); + if (value.isValid() && !value.isNull()) { + option->font = qvariant_cast<QFont>(value).resolve(option->font); + option->fontMetrics = QFontMetrics(option->font); + } + + value = index.data(Qt::TextAlignmentRole); + if (value.isValid() && !value.isNull()) + option->displayAlignment = (Qt::Alignment)value.toInt(); + + value = index.data(Qt::ForegroundRole); + if (qVariantCanConvert<QBrush>(value)) + option->palette.setBrush(QPalette::Text, qvariant_cast<QBrush>(value)); + + if (QStyleOptionViewItemV4 *v4 = qstyleoption_cast<QStyleOptionViewItemV4 *>(option)) { + v4->index = index; + QVariant value = index.data(Qt::CheckStateRole); + if (value.isValid() && !value.isNull()) { + v4->features |= QStyleOptionViewItemV2::HasCheckIndicator; + v4->checkState = static_cast<Qt::CheckState>(value.toInt()); + } + + value = index.data(Qt::DecorationRole); + if (value.isValid() && !value.isNull()) { + v4->features |= QStyleOptionViewItemV2::HasDecoration; + switch (value.type()) { + case QVariant::Icon: { + v4->icon = qvariant_cast<QIcon>(value); + QIcon::Mode mode; + if (!(option->state & QStyle::State_Enabled)) + mode = QIcon::Disabled; + else if (option->state & QStyle::State_Selected) + mode = QIcon::Selected; + else + mode = QIcon::Normal; + QIcon::State state = option->state & QStyle::State_Open ? QIcon::On : QIcon::Off; + v4->decorationSize = v4->icon.actualSize(option->decorationSize, mode, state); + break; + } + case QVariant::Color: { + QPixmap pixmap(option->decorationSize); + pixmap.fill(qvariant_cast<QColor>(value)); + v4->icon = QIcon(pixmap); + break; + } + case QVariant::Image: { + QImage image = qvariant_cast<QImage>(value); + v4->icon = QIcon(QPixmap::fromImage(image)); + v4->decorationSize = image.size(); + break; + } + case QVariant::Pixmap: { + QPixmap pixmap = qvariant_cast<QPixmap>(value); + v4->icon = QIcon(pixmap); + v4->decorationSize = pixmap.size(); + break; + } + default: + break; + } + } + + value = index.data(Qt::DisplayRole); + if (value.isValid() && !value.isNull()) { + v4->features |= QStyleOptionViewItemV2::HasDisplay; + v4->text = displayText(value, v4->locale); + } + + v4->backgroundBrush = qvariant_cast<QBrush>(index.data(Qt::BackgroundRole)); + } +} + +/*! + Renders the delegate using the given \a painter and style \a option for + the item specified by \a index. + + This function paints the item using the view's QStyle. + + When reimplementing paint in a subclass. Use the initStyleOption() + to set up the \a option in the same way as the + QStyledItemDelegate; the option will always be an instance of + QStyleOptionViewItemV4. Please see its class description for + information on its contents. + + Whenever possible, use the \a option while painting. + Especially its \l{QStyleOption::}{rect} variable to decide + where to draw and its \l{QStyleOption::}{state} to determine + if it is enabled or selected. + + After painting, you should ensure that the painter is returned to + its the state it was supplied in when this function was called. + For example, it may be useful to call QPainter::save() before + painting and QPainter::restore() afterwards. + + \sa QItemDelegate::paint(), QStyle::drawControl(), QStyle::CE_ItemViewItem +*/ +void QStyledItemDelegate::paint(QPainter *painter, + const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + Q_ASSERT(index.isValid()); + + QStyleOptionViewItemV4 opt = option; + initStyleOption(&opt, index); + + const QWidget *widget = QStyledItemDelegatePrivate::widget(option); + QStyle *style = widget ? widget->style() : QApplication::style(); + style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget); +} + +/*! + Returns the size needed by the delegate to display the item + specified by \a index, taking into account the style information + provided by \a option. + + This function uses the view's QStyle to determine the size of the + item. + + \sa QStyle::sizeFromContents(), QStyle::CT_ItemViewItem +*/ +QSize QStyledItemDelegate::sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QVariant value = index.data(Qt::SizeHintRole); + if (value.isValid()) + return qvariant_cast<QSize>(value); + + QStyleOptionViewItemV4 opt = option; + initStyleOption(&opt, index); + const QWidget *widget = QStyledItemDelegatePrivate::widget(option); + QStyle *style = widget ? widget->style() : QApplication::style(); + return style->sizeFromContents(QStyle::CT_ItemViewItem, &opt, QSize(), widget); +} + +/*! + Returns the widget used to edit the item specified by \a index + for editing. The \a parent widget and style \a option are used to + control how the editor widget appears. + + \sa QAbstractItemDelegate::createEditor() +*/ +QWidget *QStyledItemDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem &, + const QModelIndex &index) const +{ + Q_D(const QStyledItemDelegate); + if (!index.isValid()) + return 0; + QVariant::Type t = static_cast<QVariant::Type>(index.data(Qt::EditRole).userType()); + return d->editorFactory()->createEditor(t, parent); +} + +/*! + Sets the data to be displayed and edited by the \a editor from the + data model item specified by the model \a index. + + The default implementation stores the data in the \a editor + widget's \l {Qt's Property System} {user property}. + + \sa QMetaProperty::isUser() +*/ +void QStyledItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const +{ +#ifdef QT_NO_PROPERTIES + Q_UNUSED(editor); + Q_UNUSED(index); +#else + Q_D(const QStyledItemDelegate); + QVariant v = index.data(Qt::EditRole); + QByteArray n = editor->metaObject()->userProperty().name(); + + // ### Qt 5: remove + // A work-around for missing "USER true" in qdatetimeedit.h for + // QTimeEdit's time property and QDateEdit's date property. + // It only triggers if the default user property "dateTime" is + // reported for QTimeEdit and QDateEdit. + if (n == "dateTime") { + if (editor->inherits("QTimeEdit")) + n = "time"; + else if (editor->inherits("QDateEdit")) + n = "date"; + } + + // ### Qt 5: give QComboBox a USER property + if (n.isEmpty() && editor->inherits("QComboBox")) + n = d->editorFactory()->valuePropertyName(static_cast<QVariant::Type>(v.userType())); + if (!n.isEmpty()) { + if (!v.isValid()) + v = QVariant(editor->property(n).userType(), (const void *)0); + editor->setProperty(n, v); + } +#endif +} + +/*! + Gets data drom the \a editor widget and stores it in the specified + \a model at the item \a index. + + The default implementation gets the value to be stored in the data + model from the \a editor widget's \l {Qt's Property System} {user + property}. + + \sa QMetaProperty::isUser() +*/ +void QStyledItemDelegate::setModelData(QWidget *editor, + QAbstractItemModel *model, + const QModelIndex &index) const +{ +#ifdef QT_NO_PROPERTIES + Q_UNUSED(model); + Q_UNUSED(editor); + Q_UNUSED(index); +#else + Q_D(const QStyledItemDelegate); + Q_ASSERT(model); + Q_ASSERT(editor); + QByteArray n = editor->metaObject()->userProperty().name(); + if (n.isEmpty()) + n = d->editorFactory()->valuePropertyName( + static_cast<QVariant::Type>(model->data(index, Qt::EditRole).userType())); + if (!n.isEmpty()) + model->setData(index, editor->property(n), Qt::EditRole); +#endif +} + +/*! + Updates the \a editor for the item specified by \a index + according to the style \a option given. +*/ +void QStyledItemDelegate::updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + if (!editor) + return; + Q_ASSERT(index.isValid()); + const QWidget *widget = QStyledItemDelegatePrivate::widget(option); + + QStyleOptionViewItemV4 opt = option; + initStyleOption(&opt, index); + // let the editor take up all available space + //if the editor is not a QLineEdit + //or it is in a QTableView +#if !defined(QT_NO_TABLEVIEW) && !defined(QT_NO_LINEEDIT) + if (qobject_cast<QExpandingLineEdit*>(editor) && !qobject_cast<const QTableView*>(widget)) + opt.showDecorationSelected = editor->style()->styleHint(QStyle::SH_ItemView_ShowDecorationSelected, 0, editor); + else +#endif + opt.showDecorationSelected = true; + + QStyle *style = widget ? widget->style() : QApplication::style(); + QRect geom = style->subElementRect(QStyle::SE_ItemViewItemText, &opt, widget); + if ( editor->layoutDirection() == Qt::RightToLeft) { + const int delta = qSmartMinSize(editor).width() - geom.width(); + if (delta > 0) { + //we need to widen the geometry + geom.adjust(-delta, 0, 0, 0); + } + } + + editor->setGeometry(geom); +} + +/*! + Returns the editor factory used by the item delegate. + If no editor factory is set, the function will return null. + + \sa setItemEditorFactory() +*/ +QItemEditorFactory *QStyledItemDelegate::itemEditorFactory() const +{ + Q_D(const QStyledItemDelegate); + return d->factory; +} + +/*! + Sets the editor factory to be used by the item delegate to be the \a factory + specified. If no editor factory is set, the item delegate will use the + default editor factory. + + \sa itemEditorFactory() +*/ +void QStyledItemDelegate::setItemEditorFactory(QItemEditorFactory *factory) +{ + Q_D(QStyledItemDelegate); + d->factory = factory; +} + + +/*! + \fn bool QStyledItemDelegate::eventFilter(QObject *editor, QEvent *event) + + Returns true if the given \a editor is a valid QWidget and the + given \a event is handled; otherwise returns false. The following + key press events are handled by default: + + \list + \o \gui Tab + \o \gui Backtab + \o \gui Enter + \o \gui Return + \o \gui Esc + \endlist + + In the case of \gui Tab, \gui Backtab, \gui Enter and \gui Return + key press events, the \a editor's data is comitted to the model + and the editor is closed. If the \a event is a \gui Tab key press + the view will open an editor on the next item in the + view. Likewise, if the \a event is a \gui Backtab key press the + view will open an editor on the \e previous item in the view. + + If the event is a \gui Esc key press event, the \a editor is + closed \e without committing its data. + + \sa commitData(), closeEditor() +*/ +bool QStyledItemDelegate::eventFilter(QObject *object, QEvent *event) +{ + QWidget *editor = qobject_cast<QWidget*>(object); + if (!editor) + return false; + if (event->type() == QEvent::KeyPress) { + switch (static_cast<QKeyEvent *>(event)->key()) { + case Qt::Key_Tab: + emit commitData(editor); + emit closeEditor(editor, QAbstractItemDelegate::EditNextItem); + return true; + case Qt::Key_Backtab: + emit commitData(editor); + emit closeEditor(editor, QAbstractItemDelegate::EditPreviousItem); + return true; + case Qt::Key_Enter: + case Qt::Key_Return: +#ifndef QT_NO_TEXTEDIT + if (qobject_cast<QTextEdit*>(editor)) + return false; // don't filter enter key events for QTextEdit + // We want the editor to be able to process the key press + // before committing the data (e.g. so it can do + // validation/fixup of the input). +#endif // QT_NO_TEXTEDIT +#ifndef QT_NO_LINEEDIT + if (QLineEdit *e = qobject_cast<QLineEdit*>(editor)) + if (!e->hasAcceptableInput()) + return false; +#endif // QT_NO_LINEEDIT + QMetaObject::invokeMethod(this, "_q_commitDataAndCloseEditor", + Qt::QueuedConnection, Q_ARG(QWidget*, editor)); + return false; + case Qt::Key_Escape: + // don't commit data + emit closeEditor(editor, QAbstractItemDelegate::RevertModelCache); + break; + default: + return false; + } + if (editor->parentWidget()) + editor->parentWidget()->setFocus(); + return true; + } else if (event->type() == QEvent::FocusOut || event->type() == QEvent::Hide) { + //the Hide event will take care of he editors that are in fact complete dialogs + if (!editor->isActiveWindow() || (QApplication::focusWidget() != editor)) { + QWidget *w = QApplication::focusWidget(); + while (w) { // don't worry about focus changes internally in the editor + if (w == editor) + return false; + w = w->parentWidget(); + } +#ifndef QT_NO_DRAGANDDROP + // The window may lose focus during an drag operation. + // i.e when dragging involves the taskbar on Windows. + if (QDragManager::self() && QDragManager::self()->object != 0) + return false; +#endif + // Opening a modal dialog will start a new eventloop + // that will process the deleteLater event. + QWidget *activeModalWidget = QApplication::activeModalWidget(); + if (activeModalWidget + && !activeModalWidget->isAncestorOf(editor) + && qobject_cast<QDialog*>(activeModalWidget)) + return false; + emit commitData(editor); + emit closeEditor(editor, NoHint); + } + } else if (event->type() == QEvent::ShortcutOverride) { + if (static_cast<QKeyEvent*>(event)->key() == Qt::Key_Escape) { + event->accept(); + return true; + } + } + return false; +} + +/*! + \reimp +*/ +bool QStyledItemDelegate::editorEvent(QEvent *event, + QAbstractItemModel *model, + const QStyleOptionViewItem &option, + const QModelIndex &index) +{ + Q_ASSERT(event); + Q_ASSERT(model); + + // make sure that the item is checkable + Qt::ItemFlags flags = model->flags(index); + if (!(flags & Qt::ItemIsUserCheckable) || !(option.state & QStyle::State_Enabled) + || !(flags & Qt::ItemIsEnabled)) + return false; + + // make sure that we have a check state + QVariant value = index.data(Qt::CheckStateRole); + if (!value.isValid()) + return false; + + const QWidget *widget = QStyledItemDelegatePrivate::widget(option); + QStyle *style = widget ? widget->style() : QApplication::style(); + + // make sure that we have the right event type + if ((event->type() == QEvent::MouseButtonRelease) + || (event->type() == QEvent::MouseButtonDblClick)) { + QStyleOptionViewItemV4 viewOpt(option); + initStyleOption(&viewOpt, index); + QRect checkRect = style->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, &viewOpt, widget); + QMouseEvent *me = static_cast<QMouseEvent*>(event); + if (me->button() != Qt::LeftButton || !checkRect.contains(me->pos())) + return false; + + // eat the double click events inside the check rect + if (event->type() == QEvent::MouseButtonDblClick) + return true; + + } else if (event->type() == QEvent::KeyPress) { + if (static_cast<QKeyEvent*>(event)->key() != Qt::Key_Space + && static_cast<QKeyEvent*>(event)->key() != Qt::Key_Select) + return false; + } else { + return false; + } + + Qt::CheckState state = (static_cast<Qt::CheckState>(value.toInt()) == Qt::Checked + ? Qt::Unchecked : Qt::Checked); + return model->setData(index, state, Qt::CheckStateRole); +} + +QT_END_NAMESPACE + +#include "moc_qstyleditemdelegate.cpp" + +#endif // QT_NO_ITEMVIEWS diff --git a/src/gui/itemviews/qstyleditemdelegate.h b/src/gui/itemviews/qstyleditemdelegate.h new file mode 100644 index 0000000..dee3ca9 --- /dev/null +++ b/src/gui/itemviews/qstyleditemdelegate.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTYLEDITEMDELEGATE_H +#define QSTYLEDITEMDELEGATE_H + +#include <QtGui/qabstractitemdelegate.h> +#include <QtCore/qstring.h> +#include <QtGui/qpixmap.h> +#include <QtCore/qvariant.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_ITEMVIEWS + +class QStyledItemDelegatePrivate; +class QItemEditorFactory; + +class Q_GUI_EXPORT QStyledItemDelegate : public QAbstractItemDelegate +{ + Q_OBJECT + +public: + explicit QStyledItemDelegate(QObject *parent = 0); + ~QStyledItemDelegate(); + + // painting + void paint(QPainter *painter, + const QStyleOptionViewItem &option, const QModelIndex &index) const; + QSize sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + // editing + QWidget *createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + void setEditorData(QWidget *editor, const QModelIndex &index) const; + void setModelData(QWidget *editor, + QAbstractItemModel *model, + const QModelIndex &index) const; + + void updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + // editor factory + QItemEditorFactory *itemEditorFactory() const; + void setItemEditorFactory(QItemEditorFactory *factory); + + virtual QString displayText(const QVariant &value, const QLocale &locale) const; + +protected: + virtual void initStyleOption(QStyleOptionViewItem *option, + const QModelIndex &index) const; + + bool eventFilter(QObject *object, QEvent *event); + bool editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, const QModelIndex &index); + +private: + Q_DECLARE_PRIVATE(QStyledItemDelegate) + Q_DISABLE_COPY(QStyledItemDelegate) + + Q_PRIVATE_SLOT(d_func(), void _q_commitDataAndCloseEditor(QWidget*)) +}; + +#endif // QT_NO_ITEMVIEWS + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSTYLEDITEMDELEGATE_H diff --git a/src/gui/itemviews/qtableview.cpp b/src/gui/itemviews/qtableview.cpp new file mode 100644 index 0000000..34bb180 --- /dev/null +++ b/src/gui/itemviews/qtableview.cpp @@ -0,0 +1,2525 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtableview.h" + +#ifndef QT_NO_TABLEVIEW +#include <qheaderview.h> +#include <qitemdelegate.h> +#include <qapplication.h> +#include <qpainter.h> +#include <qstyle.h> +#include <qsize.h> +#include <qevent.h> +#include <qbitarray.h> +#include <qscrollbar.h> +#include <qabstractbutton.h> +#include <private/qtableview_p.h> +#ifndef QT_NO_ACCESSIBILITY +#include <qaccessible.h> +#endif + +QT_BEGIN_NAMESPACE + +class QTableCornerButton : public QAbstractButton +{ + Q_OBJECT +public: + QTableCornerButton(QWidget *parent) : QAbstractButton(parent) {} + void paintEvent(QPaintEvent*) { + QStyleOptionHeader opt; + opt.init(this); + QStyle::State state = QStyle::State_None; + if (isEnabled()) + state |= QStyle::State_Enabled; + if (isActiveWindow()) + state |= QStyle::State_Active; + if (isDown()) + state |= QStyle::State_Sunken; + opt.state = state; + opt.rect = rect(); + opt.position = QStyleOptionHeader::OnlyOneSection; + QPainter painter(this); + style()->drawControl(QStyle::CE_Header, &opt, &painter, this); + } +}; + +void QTableViewPrivate::init() +{ + Q_Q(QTableView); + + q->setEditTriggers(editTriggers|QAbstractItemView::AnyKeyPressed); + + QHeaderView *vertical = new QHeaderView(Qt::Vertical, q); + vertical->setClickable(true); + vertical->setHighlightSections(true); + q->setVerticalHeader(vertical); + + QHeaderView *horizontal = new QHeaderView(Qt::Horizontal, q); + horizontal->setClickable(true); + horizontal->setHighlightSections(true); + q->setHorizontalHeader(horizontal); + + tabKeyNavigation = true; + + cornerWidget = new QTableCornerButton(q); + cornerWidget->setFocusPolicy(Qt::NoFocus); + QObject::connect(cornerWidget, SIGNAL(clicked()), q, SLOT(selectAll())); +} + +/*! + \internal + Trims away indices that are hidden in the treeview due to hidden horizontal or vertical sections. +*/ +void QTableViewPrivate::trimHiddenSelections(QItemSelectionRange *range) const +{ + Q_ASSERT(range && range->isValid()); + + int top = range->top(); + int left = range->left(); + int bottom = range->bottom(); + int right = range->right(); + + while (bottom >= top && verticalHeader->isSectionHidden(bottom)) + --bottom; + while (right >= left && horizontalHeader->isSectionHidden(right)) + --right; + + if (top > bottom || left > right) { // everything is hidden + *range = QItemSelectionRange(); + return; + } + + while (verticalHeader->isSectionHidden(top) && top <= bottom) + ++top; + while (horizontalHeader->isSectionHidden(left) && left <= right) + ++left; + + if (top > bottom || left > right) { // everything is hidden + *range = QItemSelectionRange(); + return; + } + + QModelIndex bottomRight = model->index(bottom, right, range->parent()); + QModelIndex topLeft = model->index(top, left, range->parent()); + *range = QItemSelectionRange(topLeft, bottomRight); +} + +/*! + \internal + Sets the span for the cell at (\a row, \a column). +*/ +void QTableViewPrivate::setSpan(int row, int column, int rowSpan, int columnSpan) +{ + if (row < 0 || column < 0 || rowSpan < 0 || columnSpan < 0) + return; + Span sp(row, column, rowSpan, columnSpan); + QList<Span>::iterator it; + for (it = spans.begin(); it != spans.end(); ++it) { + if (((*it).top() == sp.top()) && ((*it).left() == sp.left())) { + if ((sp.height() == 1) && (sp.width() == 1)) + spans.erase(it); // "Implicit" span (1, 1), no need to store it + else + *it = sp; // Replace + return; + } + } + spans.append(sp); +} + +/*! + \internal + Gets the span information for the cell at (\a row, \a column). +*/ +QTableViewPrivate::Span QTableViewPrivate::span(int row, int column) const +{ + QList<Span>::const_iterator it; + for (it = spans.constBegin(); it != spans.constEnd(); ++it) { + Span span = *it; + if (isInSpan(row, column, span)) + return span; + } + return Span(row, column, 1, 1); +} + +/*! + \internal + Returns the logical index of the last section that's part of the span. +*/ +int QTableViewPrivate::sectionSpanEndLogical(const QHeaderView *header, int logical, int span) const +{ + int visual = header->visualIndex(logical); + for (int i = 1; i < span; ) { + if (++visual >= header->count()) + break; + logical = header->logicalIndex(visual); + ++i; + } + return logical; +} + +/*! + \internal + Returns the size of the span starting at logical index \a logical + and spanning \a span sections. +*/ +int QTableViewPrivate::sectionSpanSize(const QHeaderView *header, int logical, int span) const +{ + int endLogical = sectionSpanEndLogical(header, logical, span); + return header->sectionPosition(endLogical) + - header->sectionPosition(logical) + + header->sectionSize(endLogical); +} + +/*! + \internal + Returns true if the section at logical index \a logical is part of the span + starting at logical index \a spanLogical and spanning \a span sections; + otherwise, returns false. +*/ +bool QTableViewPrivate::spanContainsSection(const QHeaderView *header, int logical, int spanLogical, int span) const +{ + if (logical == spanLogical) + return true; // it's the start of the span + int visual = header->visualIndex(spanLogical); + for (int i = 1; i < span; ) { + if (++visual >= header->count()) + break; + spanLogical = header->logicalIndex(visual); + if (logical == spanLogical) + return true; + ++i; + } + return false; +} + +/*! + \internal + Returns true if one or more spans intersect column \a column. +*/ +bool QTableViewPrivate::spansIntersectColumn(int column) const +{ + QList<Span>::const_iterator it; + for (it = spans.constBegin(); it != spans.constEnd(); ++it) { + Span span = *it; + if (spanContainsColumn(column, span.left(), span.width())) + return true; + } + return false; +} + +/*! + \internal + Returns true if one or more spans intersect row \a row. +*/ +bool QTableViewPrivate::spansIntersectRow(int row) const +{ + QList<Span>::const_iterator it; + for (it = spans.constBegin(); it != spans.constEnd(); ++it) { + Span span = *it; + if (spanContainsRow(row, span.top(), span.height())) + return true; + } + return false; +} + +/*! + \internal + Returns true if one or more spans intersect one or more columns. +*/ +bool QTableViewPrivate::spansIntersectColumns(const QList<int> &columns) const +{ + QList<int>::const_iterator it; + for (it = columns.constBegin(); it != columns.constEnd(); ++it) { + if (spansIntersectColumn(*it)) + return true; + } + return false; +} + +/*! + \internal + Returns true if one or more spans intersect one or more rows. +*/ +bool QTableViewPrivate::spansIntersectRows(const QList<int> &rows) const +{ + QList<int>::const_iterator it; + for (it = rows.constBegin(); it != rows.constEnd(); ++it) { + if (spansIntersectRow(*it)) + return true; + } + return false; +} + +/*! + \internal + Returns the visual rect for the given \a span. +*/ +QRect QTableViewPrivate::visualSpanRect(const Span &span) const +{ + Q_Q(const QTableView); + // vertical + int row = span.top(); + int rowp = verticalHeader->sectionViewportPosition(row); + int rowh = rowSpanHeight(row, span.height()); + // horizontal + int column = span.left(); + int colw = columnSpanWidth(column, span.width()); + if (q->isRightToLeft()) + column = span.right(); + int colp = horizontalHeader->sectionViewportPosition(column); + + const int i = showGrid ? 1 : 0; + if (q->isRightToLeft()) + return QRect(colp + i, rowp, colw - i, rowh - i); + return QRect(colp, rowp, colw - i, rowh - i); +} + +/*! + \internal + Draws the spanning cells within rect \a area, and clips them off as + preparation for the main drawing loop. + \a drawn is a QBitArray of visualRowCountxvisualCoulumnCount which say if particular cell has been drawn +*/ +void QTableViewPrivate::drawAndClipSpans(const QRect &area, QPainter *painter, + const QStyleOptionViewItemV4 &option, + QBitArray *drawn) +{ + bool alternateBase = false; + QRegion region = viewport->rect(); + + int firstVisualRow = qMax(verticalHeader->visualIndexAt(0),0); + int lastVisualRow = verticalHeader->visualIndexAt(viewport->height()); + if (lastVisualRow == -1) + lastVisualRow = model->rowCount(root) - 1; + + int firstVisualColumn = horizontalHeader->visualIndexAt(0); + int lastVisualColumn = horizontalHeader->visualIndexAt(viewport->width()); + if (q_func()->isRightToLeft()) + qSwap(firstVisualColumn, lastVisualColumn); + if (firstVisualColumn == -1) + firstVisualColumn = 0; + if (lastVisualColumn == -1) + lastVisualColumn = model->columnCount(root) - 1; + + QList<Span>::const_iterator it; + for (it = spans.constBegin(); it != spans.constEnd(); ++it) { + Span span = *it; + + int row = span.top(); + int col = span.left(); + if (isHidden(row, col)) + continue; + QModelIndex index = model->index(row, col, root); + if (!index.isValid()) + continue; + QRect rect = visualSpanRect(span); + rect.translate(scrollDelayOffset); + if (!rect.intersects(area)) + continue; + QStyleOptionViewItemV4 opt = option; + opt.rect = rect; + alternateBase = alternatingColors && (span.top() & 1); + if (alternateBase) + opt.features |= QStyleOptionViewItemV2::Alternate; + else + opt.features &= ~QStyleOptionViewItemV2::Alternate; + drawCell(painter, opt, index); + region -= rect; + for (int r = span.top(); r <= span.bottom(); ++r) { + const int vr = visualRow(r); + if (vr < firstVisualRow || vr > lastVisualRow) + continue; + for (int c = span.left(); c <= span.right(); ++c) { + const int vc = visualColumn(c); + if (vc < firstVisualColumn || vc > lastVisualColumn) + continue; + drawn->setBit((vr - firstVisualRow) * (lastVisualColumn - firstVisualColumn + 1) + + vc - firstVisualColumn); + } + } + + } + painter->setClipRegion(region); +} + +/*! + \internal + Draws a table cell. +*/ +void QTableViewPrivate::drawCell(QPainter *painter, const QStyleOptionViewItemV4 &option, const QModelIndex &index) +{ + Q_Q(QTableView); + QStyleOptionViewItemV4 opt = option; + + if (selectionModel && selectionModel->isSelected(index)) + opt.state |= QStyle::State_Selected; + if (index == hover) + opt.state |= QStyle::State_MouseOver; + if (option.state & QStyle::State_Enabled) { + QPalette::ColorGroup cg; + if ((model->flags(index) & Qt::ItemIsEnabled) == 0) { + opt.state &= ~QStyle::State_Enabled; + cg = QPalette::Disabled; + } else { + cg = QPalette::Normal; + } + opt.palette.setCurrentColorGroup(cg); + } + + if (index == q->currentIndex()) { + const bool focus = (q->hasFocus() || viewport->hasFocus()) && q->currentIndex().isValid(); + if (focus) + opt.state |= QStyle::State_HasFocus; + } + + if (opt.features & QStyleOptionViewItemV2::Alternate) + painter->fillRect(opt.rect, opt.palette.brush(QPalette::AlternateBase)); + + if (const QWidget *widget = editorForIndex(index).editor) { + painter->save(); + painter->setClipRect(widget->geometry()); + q->itemDelegate(index)->paint(painter, opt, index); + painter->restore(); + } else { + q->itemDelegate(index)->paint(painter, opt, index); + } +} + +/*! + \class QTableView + + \brief The QTableView class provides a default model/view + implementation of a table view. + + \ingroup model-view + \ingroup advanced + \mainclass + + A QTableView implements a table view that displays items from a + model. This class is used to provide standard tables that were + previously provided by the QTable class, but using the more + flexible approach provided by Qt's model/view architecture. + + The QTableView class is one of the \l{Model/View Classes} + and is part of Qt's \l{Model/View Programming}{model/view framework}. + + QTableView implements the interfaces defined by the + QAbstractItemView class to allow it to display data provided by + models derived from the QAbstractItemModel class. + + \section1 Navigation + + You can navigate the cells in the table by clicking on a cell with the + mouse, or by using the arrow keys. Because QTableView enables + \l{QAbstractItemView::tabKeyNavigation}{tabKeyNavigation} by default, you + can also hit Tab and Backtab to move from cell to cell. + + \section1 Visual Appearance + + The table has a vertical header that can be obtained using the + verticalHeader() function, and a horizontal header that is available + through the horizontalHeader() function. The height of each row in the + table can be found by using rowHeight(); similarly, the width of + columns can be found using columnWidth(). Since both of these are plain + widgets, you can hide either of them using their hide() functions. + + Rows and columns can be hidden and shown with hideRow(), hideColumn(), + showRow(), and showColumn(). They can be selected with selectRow() + and selectColumn(). The table will show a grid depending on the + \l showGrid property. + + The items shown in a table view, like those in the other item views, are + rendered and edited using standard \l{QItemDelegate}{delegates}. However, + for some tasks it is sometimes useful to be able to insert widgets in a + table instead. Widgets are set for particular indexes with the + \l{QAbstractItemView::}{setIndexWidget()} function, and + later retrieved with \l{QAbstractItemView::}{indexWidget()}. + + \table + \row \o \inlineimage qtableview-resized.png + \o By default, the cells in a table do not expand to fill the available space. + + You can make the cells fill the available space by stretching the last + header section. Access the relevant header using horizontalHeader() + or verticalHeader() and set the header's \l{QHeaderView::}{stretchLastSection} + property. + + To distribute the available space according to the space requirement of + each column or row, call the view's resizeColumnsToContents() or + resizeRowsToContents() functions. + \endtable + + \section1 Coordinate Systems + + For some specialized forms of tables it is useful to be able to + convert between row and column indexes and widget coordinates. + The rowAt() function provides the y-coordinate within the view of the + specified row; the row index can be used to obtain a corresponding + y-coordinate with rowViewportPosition(). The columnAt() and + columnViewportPosition() functions provide the equivalent conversion + operations between x-coordinates and column indexes. + + \section1 Styles + + QTableView is styled appropriately for each platform. The following images show + how it looks on three different platforms. Go to the \l{Qt Widget Gallery} to see + its appearance in other styles. + + \table 100% + \row \o \inlineimage windowsxp-tableview.png Screenshot of a Windows XP style table view + \o \inlineimage macintosh-tableview.png Screenshot of a Macintosh style table view + \o \inlineimage plastique-tableview.png Screenshot of a Plastique style table view + \row \o A \l{Windows XP Style Widget Gallery}{Windows XP style} table view. + \o A \l{Macintosh Style Widget Gallery}{Macintosh style} table view. + \o A \l{Plastique Style Widget Gallery}{Plastique style} table view. + \endtable + + \sa QTableWidget, {View Classes}, QAbstractItemModel, QAbstractItemView, + {Chart Example}, {Pixelator Example}, {Table Model Example} +*/ + +/*! + Constructs a table view with a \a parent to represent the data. + + \sa QAbstractItemModel +*/ + +QTableView::QTableView(QWidget *parent) + : QAbstractItemView(*new QTableViewPrivate, parent) +{ + Q_D(QTableView); + d->init(); +} + +/*! + \internal +*/ +QTableView::QTableView(QTableViewPrivate &dd, QWidget *parent) + : QAbstractItemView(dd, parent) +{ + Q_D(QTableView); + d->init(); +} + +/*! + Destroys the table view. +*/ +QTableView::~QTableView() +{ +} + +/*! + \reimp +*/ +void QTableView::setModel(QAbstractItemModel *model) +{ + Q_D(QTableView); + d->verticalHeader->setModel(model); + d->horizontalHeader->setModel(model); + QAbstractItemView::setModel(model); +} + +/*! + \reimp +*/ +void QTableView::setRootIndex(const QModelIndex &index) +{ + Q_D(QTableView); + if (index == d->root) { + viewport()->update(); + return; + } + d->verticalHeader->setRootIndex(index); + d->horizontalHeader->setRootIndex(index); + QAbstractItemView::setRootIndex(index); +} + +/*! + \reimp +*/ +void QTableView::setSelectionModel(QItemSelectionModel *selectionModel) +{ + Q_D(QTableView); + Q_ASSERT(selectionModel); + d->verticalHeader->setSelectionModel(selectionModel); + d->horizontalHeader->setSelectionModel(selectionModel); + QAbstractItemView::setSelectionModel(selectionModel); +} + +/*! + Returns the table view's horizontal header. + + \sa setHorizontalHeader(), verticalHeader(), QAbstractItemModel::headerData() +*/ +QHeaderView *QTableView::horizontalHeader() const +{ + Q_D(const QTableView); + return d->horizontalHeader; +} + +/*! + Returns the table view's vertical header. + + \sa setVerticalHeader(), horizontalHeader(), QAbstractItemModel::headerData() +*/ +QHeaderView *QTableView::verticalHeader() const +{ + Q_D(const QTableView); + return d->verticalHeader; +} + +/*! + Sets the widget to use for the horizontal header to \a header. + + \sa horizontalHeader() setVerticalHeader() +*/ +void QTableView::setHorizontalHeader(QHeaderView *header) +{ + Q_D(QTableView); + + if (!header || header == d->horizontalHeader) + return; + if (d->horizontalHeader && d->horizontalHeader->parent() == this) + delete d->horizontalHeader; + d->horizontalHeader = header; + d->horizontalHeader->setParent(this); + if (!d->horizontalHeader->model()) { + d->horizontalHeader->setModel(d->model); + if (d->selectionModel) + d->horizontalHeader->setSelectionModel(d->selectionModel); + } + + connect(d->horizontalHeader,SIGNAL(sectionResized(int,int,int)), + this, SLOT(columnResized(int,int,int))); + connect(d->horizontalHeader, SIGNAL(sectionMoved(int,int,int)), + this, SLOT(columnMoved(int,int,int))); + connect(d->horizontalHeader, SIGNAL(sectionCountChanged(int,int)), + this, SLOT(columnCountChanged(int,int))); + connect(d->horizontalHeader, SIGNAL(sectionPressed(int)), this, SLOT(selectColumn(int))); + connect(d->horizontalHeader, SIGNAL(sectionEntered(int)), this, SLOT(_q_selectColumn(int))); + connect(d->horizontalHeader, SIGNAL(sectionHandleDoubleClicked(int)), + this, SLOT(resizeColumnToContents(int))); + connect(d->horizontalHeader, SIGNAL(geometriesChanged()), this, SLOT(updateGeometries())); +} + +/*! + Sets the widget to use for the vertical header to \a header. + + \sa verticalHeader() setHorizontalHeader() +*/ +void QTableView::setVerticalHeader(QHeaderView *header) +{ + Q_D(QTableView); + + if (!header || header == d->verticalHeader) + return; + if (d->verticalHeader && d->verticalHeader->parent() == this) + delete d->verticalHeader; + d->verticalHeader = header; + d->verticalHeader->setParent(this); + if (!d->verticalHeader->model()) { + d->verticalHeader->setModel(d->model); + if (d->selectionModel) + d->verticalHeader->setSelectionModel(d->selectionModel); + } + + connect(d->verticalHeader, SIGNAL(sectionResized(int,int,int)), + this, SLOT(rowResized(int,int,int))); + connect(d->verticalHeader, SIGNAL(sectionMoved(int,int,int)), + this, SLOT(rowMoved(int,int,int))); + connect(d->verticalHeader, SIGNAL(sectionCountChanged(int,int)), + this, SLOT(rowCountChanged(int,int))); + connect(d->verticalHeader, SIGNAL(sectionPressed(int)), this, SLOT(selectRow(int))); + connect(d->verticalHeader, SIGNAL(sectionEntered(int)), this, SLOT(_q_selectRow(int))); + connect(d->verticalHeader, SIGNAL(sectionHandleDoubleClicked(int)), + this, SLOT(resizeRowToContents(int))); + connect(d->verticalHeader, SIGNAL(geometriesChanged()), this, SLOT(updateGeometries())); +} + +/*! + \internal + + Scroll the contents of the table view by (\a dx, \a dy). +*/ +void QTableView::scrollContentsBy(int dx, int dy) +{ + Q_D(QTableView); + + d->delayedAutoScroll.stop(); // auto scroll was canceled by the user scrolling + + dx = isRightToLeft() ? -dx : dx; + if (dx) { + if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) { + int oldOffset = d->horizontalHeader->offset(); + if (horizontalScrollBar()->value() == horizontalScrollBar()->maximum()) + d->horizontalHeader->setOffsetToLastSection(); + else + d->horizontalHeader->setOffsetToSectionPosition(horizontalScrollBar()->value()); + int newOffset = d->horizontalHeader->offset(); + dx = isRightToLeft() ? newOffset - oldOffset : oldOffset - newOffset; + } else { + d->horizontalHeader->setOffset(horizontalScrollBar()->value()); + } + } + if (dy) { + if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) { + int oldOffset = d->verticalHeader->offset(); + if (verticalScrollBar()->value() == verticalScrollBar()->maximum()) + d->verticalHeader->setOffsetToLastSection(); + else + d->verticalHeader->setOffsetToSectionPosition(verticalScrollBar()->value()); + int newOffset = d->verticalHeader->offset(); + dy = oldOffset - newOffset; + } else { + d->verticalHeader->setOffset(verticalScrollBar()->value()); + } + } + d->scrollContentsBy(dx, dy); + + if (d->showGrid) { + //we need to update the first line of the previous top item in the view + //because it has the grid drawn if the header is invisible. + //It is strictly related to what's done at then end of the paintEvent + if (dy > 0 && d->horizontalHeader->isHidden() && d->verticalScrollMode == ScrollPerItem) { + d->viewport->update(0, dy, d->viewport->width(), dy); + } + if (dx > 0 && d->verticalHeader->isHidden() && d->horizontalScrollMode == ScrollPerItem) { + d->viewport->update(dx, 0, dx, d->viewport->height()); + } + } +} + +/*! + \reimp +*/ +QStyleOptionViewItem QTableView::viewOptions() const +{ + QStyleOptionViewItem option = QAbstractItemView::viewOptions(); + option.showDecorationSelected = true; + return option; +} + +/*! + Paints the table on receipt of the given paint event \a event. +*/ +void QTableView::paintEvent(QPaintEvent *event) +{ + Q_D(QTableView); + // setup temp variables for the painting + QStyleOptionViewItemV4 option = d->viewOptionsV4(); + const QPoint offset = d->scrollDelayOffset; + const bool showGrid = d->showGrid; + const int gridSize = showGrid ? 1 : 0; + const int gridHint = style()->styleHint(QStyle::SH_Table_GridLineColor, &option, this); + const QColor gridColor = static_cast<QRgb>(gridHint); + const QPen gridPen = QPen(gridColor, 0, d->gridStyle); + const QHeaderView *verticalHeader = d->verticalHeader; + const QHeaderView *horizontalHeader = d->horizontalHeader; + const QStyle::State state = option.state; + const bool alternate = d->alternatingColors; + const bool rightToLeft = isRightToLeft(); + + QPainter painter(d->viewport); + + // if there's nothing to do, clear the area and return + if (horizontalHeader->count() == 0 || verticalHeader->count() == 0 || !d->itemDelegate) + return; + + uint x = horizontalHeader->length() - horizontalHeader->offset() - (rightToLeft ? 0 : 1); + uint y = verticalHeader->length() - verticalHeader->offset() - 1; + + QVector<QRect> rects = event->region().rects(); + int firstVisualRow = qMax(verticalHeader->visualIndexAt(0),0); + int lastVisualRow = verticalHeader->visualIndexAt(verticalHeader->viewport()->height()); + if (lastVisualRow == -1) + lastVisualRow = d->model->rowCount(d->root); + + int firstVisualColumn = horizontalHeader->visualIndexAt(0); + int lastVisualColumn = horizontalHeader->visualIndexAt(horizontalHeader->viewport()->width()); + if (rightToLeft) + qSwap(firstVisualColumn, lastVisualColumn); + if (firstVisualColumn == -1) + firstVisualColumn = 0; + if (lastVisualColumn == -1) + lastVisualColumn = horizontalHeader->count() - 1; + + QBitArray drawn((lastVisualRow - firstVisualRow + 1) * (lastVisualColumn - firstVisualColumn + 1)); + + for (int i = 0; i < rects.size(); ++i) { + QRect dirtyArea = rects.at(i); + dirtyArea.translate(offset); + dirtyArea.setBottom(qMin(dirtyArea.bottom(), int(y))); + if (rightToLeft) { + dirtyArea.setLeft(qMax(dirtyArea.left(), d->viewport->width() - int(x))); + } else { + dirtyArea.setRight(qMin(dirtyArea.right(), int(x))); + } + + if (d->hasSpans()) + d->drawAndClipSpans(dirtyArea, &painter, option, &drawn); + + // get the horizontal start and end visual sections + int left = horizontalHeader->visualIndexAt(dirtyArea.left()); + int right = horizontalHeader->visualIndexAt(dirtyArea.right()); + if (rightToLeft) + qSwap(left, right); + if (left == -1) left = 0; + if (right == -1) right = horizontalHeader->count() - 1; + + // get the vertical start and end visual sections and if alternate color + int bottom = verticalHeader->visualIndexAt(dirtyArea.bottom()); + if (bottom == -1) bottom = verticalHeader->count() - 1; + int top = 0; + bool alternateBase = false; + if (alternate && verticalHeader->sectionsHidden()) { + uint verticalOffset = verticalHeader->offset(); + int row = verticalHeader->logicalIndex(top); + for (int y = 0; + ((uint)(y += verticalHeader->sectionSize(top)) <= verticalOffset) && (top < bottom); + ++top) { + row = verticalHeader->logicalIndex(top); + if (alternate && !verticalHeader->isSectionHidden(row)) + alternateBase = !alternateBase; + } + } else { + top = verticalHeader->visualIndexAt(dirtyArea.top()); + alternateBase = (top & 1) && alternate; + } + if (top == -1 || top > bottom) + continue; + + // Paint each row item + for (int visualRowIndex = top; visualRowIndex <= bottom; ++visualRowIndex) { + int row = verticalHeader->logicalIndex(visualRowIndex); + if (verticalHeader->isSectionHidden(row)) + continue; + int rowY = rowViewportPosition(row); + rowY += offset.y(); + int rowh = rowHeight(row) - gridSize; + + // Paint each column item + for (int visualColumnIndex = left; visualColumnIndex <= right; ++visualColumnIndex) { + int currentBit = (visualRowIndex - firstVisualRow) * (lastVisualColumn - firstVisualColumn + 1) + + visualColumnIndex - firstVisualColumn; + + if (currentBit < 0 || currentBit >= drawn.size() || drawn.testBit(currentBit)) + continue; + drawn.setBit(currentBit); + + int col = horizontalHeader->logicalIndex(visualColumnIndex); + if (horizontalHeader->isSectionHidden(col)) + continue; + int colp = columnViewportPosition(col); + colp += offset.x(); + int colw = columnWidth(col) - gridSize; + + const QModelIndex index = d->model->index(row, col, d->root); + if (index.isValid()) { + option.rect = QRect(colp + (showGrid && rightToLeft ? 1 : 0), rowY, colw, rowh); + if (alternate) { + if (alternateBase) + option.features |= QStyleOptionViewItemV2::Alternate; + else + option.features &= ~QStyleOptionViewItemV2::Alternate; + } + d->drawCell(&painter, option, index); + } + } + alternateBase = !alternateBase && alternate; + } + + if (showGrid) { + // Find the bottom right (the last rows/coloumns might be hidden) + while (verticalHeader->isSectionHidden(verticalHeader->logicalIndex(bottom))) --bottom; + QPen old = painter.pen(); + painter.setPen(gridPen); + // Paint each row + for (int visualIndex = top; visualIndex <= bottom; ++visualIndex) { + int row = verticalHeader->logicalIndex(visualIndex); + if (verticalHeader->isSectionHidden(row)) + continue; + int rowY = rowViewportPosition(row); + rowY += offset.y(); + int rowh = rowHeight(row) - gridSize; + painter.drawLine(dirtyArea.left(), rowY + rowh, dirtyArea.right(), rowY + rowh); + } + + // Paint each column + for (int h = left; h <= right; ++h) { + int col = horizontalHeader->logicalIndex(h); + if (horizontalHeader->isSectionHidden(col)) + continue; + int colp = columnViewportPosition(col); + colp += offset.x(); + if (!rightToLeft) + colp += columnWidth(col) - gridSize; + painter.drawLine(colp, dirtyArea.top(), colp, dirtyArea.bottom()); + } + + //draw the top & left grid lines if the headers are not visible. + //We do update this line when subsequent scroll happen (see scrollContentsBy) + if (horizontalHeader->isHidden() && verticalScrollMode() == ScrollPerItem) + painter.drawLine(dirtyArea.left(), 0, dirtyArea.right(), 0); + if (verticalHeader->isHidden() && horizontalScrollMode() == ScrollPerItem) + painter.drawLine(0, dirtyArea.top(), 0, dirtyArea.bottom()); + painter.setPen(old); + } + } + +#ifndef QT_NO_DRAGANDDROP + // Paint the dropIndicator + d->paintDropIndicator(&painter); +#endif +} + +/*! + Returns the index position of the model item corresponding to the + table item at position \a pos in contents coordinates. +*/ +QModelIndex QTableView::indexAt(const QPoint &pos) const +{ + Q_D(const QTableView); + d->executePostedLayout(); + int r = rowAt(pos.y()); + int c = columnAt(pos.x()); + if (r >= 0 && c >= 0) { + if (d->hasSpans()) { + QTableViewPrivate::Span span = d->span(r, c); + r = span.top(); + c = span.left(); + } + return d->model->index(r, c, d->root); + } + return QModelIndex(); +} + +/*! + Returns the horizontal offset of the items in the table view. + + Note that the table view uses the horizontal header section + positions to determine the positions of columns in the view. + + \sa verticalOffset() +*/ +int QTableView::horizontalOffset() const +{ + Q_D(const QTableView); + return d->horizontalHeader->offset(); +} + +/*! + Returns the vertical offset of the items in the table view. + + Note that the table view uses the vertical header section + positions to determine the positions of rows in the view. + + \sa horizontalOffset() +*/ +int QTableView::verticalOffset() const +{ + Q_D(const QTableView); + return d->verticalHeader->offset(); +} + +/*! + \fn QModelIndex QTableView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) + + Moves the cursor in accordance with the given \a cursorAction, using the + information provided by the \a modifiers. + + \sa QAbstractItemView::CursorAction +*/ +QModelIndex QTableView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) +{ + Q_D(QTableView); + Q_UNUSED(modifiers); + + int bottom = d->model->rowCount(d->root) - 1; + // make sure that bottom is the bottommost *visible* row + while (bottom >= 0 && isRowHidden(d->logicalRow(bottom))) + --bottom; + + int right = d->model->columnCount(d->root) - 1; + + while (right >= 0 && isColumnHidden(d->logicalColumn(right))) + --right; + + if (bottom == -1 || right == -1) + return QModelIndex(); // model is empty + + QModelIndex current = currentIndex(); + + if (!current.isValid()) { + int row = 0; + int column = 0; + while (column < right && isColumnHidden(d->logicalColumn(column))) + ++column; + while (isRowHidden(d->logicalRow(row)) && row < bottom) + ++row; + return d->model->index(d->logicalRow(row), d->logicalColumn(column), d->root); + } + + int visualRow = d->visualRow(current.row()); + Q_ASSERT(visualRow != -1); + int visualColumn = d->visualColumn(current.column()); + Q_ASSERT(visualColumn != -1); + + if (isRightToLeft()) { + if (cursorAction == MoveLeft) + cursorAction = MoveRight; + else if (cursorAction == MoveRight) + cursorAction = MoveLeft; + } + + switch (cursorAction) { + case MoveUp: +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled() && visualRow == 0) + visualRow = d->visualRow(model()->rowCount() - 1) + 1; +#endif + --visualRow; + while (visualRow > 0 && d->isVisualRowHiddenOrDisabled(visualRow, visualColumn)) + --visualRow; + if (d->hasSpans()) { + int row = d->logicalRow(visualRow); + QTableViewPrivate::Span span = d->span(row, current.column()); + visualRow = d->visualRow(span.top()); + visualColumn = d->visualColumn(span.left()); + } + break; + case MoveDown: + if (d->hasSpans()) { + QTableViewPrivate::Span span = d->span(current.row(), current.column()); + visualRow = d->visualRow(d->rowSpanEndLogical(span.top(), span.height())); + } +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled() && visualRow >= bottom) + visualRow = -1; +#endif + ++visualRow; + while (visualRow < bottom && d->isVisualRowHiddenOrDisabled(visualRow, visualColumn)) + ++visualRow; + if (d->hasSpans()) { + int row = d->logicalRow(visualRow); + QTableViewPrivate::Span span = d->span(row, current.column()); + visualColumn = d->visualColumn(span.left()); + } + break; + case MovePrevious: { + int left = 0; + while (d->isVisualColumnHiddenOrDisabled(visualRow, left) && left < right) + ++left; + if (visualColumn == left) { + visualColumn = right; + int top = 0; + while (top < bottom && d->isVisualRowHiddenOrDisabled(top, visualColumn)) + ++top; + if (visualRow == top) + visualRow = bottom; + else + --visualRow; + while (visualRow > 0 && d->isVisualRowHiddenOrDisabled(visualRow, visualColumn)) + --visualRow; + break; + } // else MoveLeft + } + case MoveLeft: + --visualColumn; + while (visualColumn > 0 && d->isVisualColumnHiddenOrDisabled(visualRow, visualColumn)) + --visualColumn; + if (d->hasSpans()) { + int column = d->logicalColumn(visualColumn); + QTableViewPrivate::Span span = d->span(current.row(), column); + visualRow = d->visualRow(span.top()); + visualColumn = d->visualColumn(span.left()); + } + break; + case MoveNext: + if (visualColumn == right) { + visualColumn = 0; + while (visualColumn < right && d->isVisualColumnHiddenOrDisabled(visualRow, visualColumn)) + ++visualColumn; + if (visualRow == bottom) + visualRow = 0; + else + ++visualRow; + while (visualRow < bottom && d->isVisualRowHiddenOrDisabled(visualRow, visualColumn)) + ++visualRow; + break; + } // else MoveRight + case MoveRight: + if (d->hasSpans()) { + QTableViewPrivate::Span span = d->span(current.row(), current.column()); + visualColumn = d->visualColumn(d->columnSpanEndLogical(span.left(), span.width())); + } + ++visualColumn; + while (visualColumn < right && d->isVisualColumnHiddenOrDisabled(visualRow, visualColumn)) + ++visualColumn; + if (d->hasSpans()) { + int column = d->logicalColumn(visualColumn); + QTableViewPrivate::Span span = d->span(current.row(), column); + visualRow = d->visualRow(span.top()); + } + break; + case MoveHome: + visualColumn = 0; + while (visualColumn < right && d->isVisualColumnHiddenOrDisabled(visualRow, visualColumn)) + ++visualColumn; + if (modifiers & Qt::ControlModifier) { + visualRow = 0; + while (visualRow < bottom && d->isVisualRowHiddenOrDisabled(visualRow, visualColumn)) + ++visualRow; + } + break; + case MoveEnd: + visualColumn = right; + if (modifiers & Qt::ControlModifier) + visualRow = bottom; + break; + case MovePageUp: { + int top = 0; + while (top < bottom && d->isVisualRowHiddenOrDisabled(top, visualColumn)) + ++top; + int newRow = qMax(rowAt(visualRect(current).top() - d->viewport->height()), top); + return d->model->index(qBound(0, newRow, bottom), current.column(), d->root); + } + case MovePageDown: { + int newRow = qMin(rowAt(visualRect(current).bottom() + d->viewport->height()), bottom); + if (newRow < 0) + newRow = bottom; + return d->model->index(qBound(0, newRow, bottom), current.column(), d->root); + }} + + int logicalRow = d->logicalRow(visualRow); + int logicalColumn = d->logicalColumn(visualColumn); + if (!d->model->hasIndex(logicalRow, logicalColumn, d->root)) + return QModelIndex(); + + QModelIndex result = d->model->index(logicalRow, logicalColumn, d->root); + if (!isIndexHidden(result) && d->isIndexEnabled(result)) + return d->model->index(logicalRow, logicalColumn, d->root); + + return QModelIndex(); +} + +/*! + \fn void QTableView::setSelection(const QRect &rect, + QItemSelectionModel::SelectionFlags flags) + + Selects the items within the given \a rect and in accordance with + the specified selection \a flags. +*/ +void QTableView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command) +{ + Q_D(QTableView); + QModelIndex tl = indexAt(QPoint(isRightToLeft() ? qMax(rect.left(), rect.right()) + : qMin(rect.left(), rect.right()), qMin(rect.top(), rect.bottom()))); + QModelIndex br = indexAt(QPoint(isRightToLeft() ? qMin(rect.left(), rect.right()) : + qMax(rect.left(), rect.right()), qMax(rect.top(), rect.bottom()))); + if (!d->selectionModel || !tl.isValid() || !br.isValid() || !d->isIndexEnabled(tl) || !d->isIndexEnabled(br)) + return; + + bool verticalMoved = verticalHeader()->sectionsMoved(); + bool horizontalMoved = horizontalHeader()->sectionsMoved(); + + QItemSelection selection; + + if (d->hasSpans()) { + bool expanded; + int top = qMin(d->visualRow(tl.row()), d->visualRow(br.row())); + int left = qMin(d->visualColumn(tl.column()), d->visualColumn(br.column())); + int bottom = qMax(d->visualRow(tl.row()), d->visualRow(br.row())); + int right = qMax(d->visualColumn(tl.column()), d->visualColumn(br.column())); + do { + expanded = false; + QList<QTableViewPrivate::Span>::const_iterator it; + for (it = d->spans.constBegin(); it != d->spans.constEnd(); ++it) { + QTableViewPrivate::Span span = *it; + int t = d->visualRow(span.top()); + int l = d->visualColumn(span.left()); + int b = d->visualRow(d->rowSpanEndLogical(span.top(), span.height())); + int r = d->visualColumn(d->columnSpanEndLogical(span.left(), span.width())); + if ((t > bottom) || (l > right) || (top > b) || (left > r)) + continue; // no intersect + if (t < top) { + top = t; + expanded = true; + } + if (l < left) { + left = l; + expanded = true; + } + if (b > bottom) { + bottom = b; + expanded = true; + } + if (r > right) { + right = r; + expanded = true; + } + if (expanded) + break; + } + } while (expanded); + for (int horizontal = left; horizontal <= right; ++horizontal) { + int column = d->logicalColumn(horizontal); + for (int vertical = top; vertical <= bottom; ++vertical) { + int row = d->logicalRow(vertical); + QModelIndex index = d->model->index(row, column, d->root); + selection.append(QItemSelectionRange(index)); + } + } + } else if (verticalMoved && horizontalMoved) { + int top = d->visualRow(tl.row()); + int left = d->visualColumn(tl.column()); + int bottom = d->visualRow(br.row()); + int right = d->visualColumn(br.column()); + for (int horizontal = left; horizontal <= right; ++horizontal) { + int column = d->logicalColumn(horizontal); + for (int vertical = top; vertical <= bottom; ++vertical) { + int row = d->logicalRow(vertical); + QModelIndex index = d->model->index(row, column, d->root); + selection.append(QItemSelectionRange(index)); + } + } + } else if (horizontalMoved) { + int left = d->visualColumn(tl.column()); + int right = d->visualColumn(br.column()); + for (int visual = left; visual <= right; ++visual) { + int column = d->logicalColumn(visual); + QModelIndex topLeft = d->model->index(tl.row(), column, d->root); + QModelIndex bottomRight = d->model->index(br.row(), column, d->root); + selection.append(QItemSelectionRange(topLeft, bottomRight)); + } + } else if (verticalMoved) { + int top = d->visualRow(tl.row()); + int bottom = d->visualRow(br.row()); + for (int visual = top; visual <= bottom; ++visual) { + int row = d->logicalRow(visual); + QModelIndex topLeft = d->model->index(row, tl.column(), d->root); + QModelIndex bottomRight = d->model->index(row, br.column(), d->root); + selection.append(QItemSelectionRange(topLeft, bottomRight)); + } + } else { // nothing moved + selection.append(QItemSelectionRange(tl, br)); + } + + d->selectionModel->select(selection, command); +} + +/*! + \internal + + Returns the rectangle from the viewport of the items in the given + \a selection. +*/ +QRegion QTableView::visualRegionForSelection(const QItemSelection &selection) const +{ + Q_D(const QTableView); + + if (selection.isEmpty()) + return QRegion(); + + QRegion selectionRegion; + bool verticalMoved = verticalHeader()->sectionsMoved(); + bool horizontalMoved = horizontalHeader()->sectionsMoved(); + + if ((verticalMoved && horizontalMoved) || d->hasSpans()) { + for (int i = 0; i < selection.count(); ++i) { + QItemSelectionRange range = selection.at(i); + if (range.parent() != d->root || !range.isValid()) + continue; + for (int r = range.top(); r <= range.bottom(); ++r) + for (int c = range.left(); c <= range.right(); ++c) + selectionRegion += QRegion(visualRect(d->model->index(r, c, d->root))); + } + } else if (horizontalMoved) { + for (int i = 0; i < selection.count(); ++i) { + QItemSelectionRange range = selection.at(i); + if (range.parent() != d->root || !range.isValid()) + continue; + int top = rowViewportPosition(range.top()); + int bottom = rowViewportPosition(range.bottom()) + rowHeight(range.bottom()); + if (top > bottom) + qSwap<int>(top, bottom); + int height = bottom - top; + for (int c = range.left(); c <= range.right(); ++c) + selectionRegion += QRegion(QRect(columnViewportPosition(c), top, + columnWidth(c), height)); + } + } else if (verticalMoved) { + for (int i = 0; i < selection.count(); ++i) { + QItemSelectionRange range = selection.at(i); + if (range.parent() != d->root || !range.isValid()) + continue; + int left = columnViewportPosition(range.left()); + int right = columnViewportPosition(range.right()) + columnWidth(range.right()); + if (left > right) + qSwap<int>(left, right); + int width = right - left; + for (int r = range.top(); r <= range.bottom(); ++r) + selectionRegion += QRegion(QRect(left, rowViewportPosition(r), + width, rowHeight(r))); + } + } else { // nothing moved + for (int i = 0; i < selection.count(); ++i) { + QItemSelectionRange range = selection.at(i); + if (range.parent() != d->root || !range.isValid()) + continue; + d->trimHiddenSelections(&range); + QRect tl = visualRect(range.topLeft()); + QRect br = visualRect(range.bottomRight()); + selectionRegion += QRegion(tl|br); + } + } + + return selectionRegion; +} + + +/*! + \reimp +*/ +QModelIndexList QTableView::selectedIndexes() const +{ + Q_D(const QTableView); + QModelIndexList viewSelected; + QModelIndexList modelSelected; + if (d->selectionModel) + modelSelected = d->selectionModel->selectedIndexes(); + for (int i = 0; i < modelSelected.count(); ++i) { + QModelIndex index = modelSelected.at(i); + if (!isIndexHidden(index) && index.parent() == d->root) + viewSelected.append(index); + } + return viewSelected; +} + + +/*! + This slot is called whenever rows are added or deleted. The + previous number of rows is specified by \a oldCount, and the new + number of rows is specified by \a newCount. +*/ +void QTableView::rowCountChanged(int /*oldCount*/, int /*newCount*/ ) +{ + Q_D(QTableView); + updateGeometries(); + if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) + d->verticalHeader->setOffsetToSectionPosition(verticalScrollBar()->value()); + else + d->verticalHeader->setOffset(verticalScrollBar()->value()); + d->viewport->update(); +} + +/*! + This slot is called whenever columns are added or deleted. The + previous number of columns is specified by \a oldCount, and the new + number of columns is specified by \a newCount. +*/ +void QTableView::columnCountChanged(int, int) +{ + Q_D(QTableView); + updateGeometries(); + if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) + d->horizontalHeader->setOffsetToSectionPosition(horizontalScrollBar()->value()); + else + d->horizontalHeader->setOffset(horizontalScrollBar()->value()); + d->viewport->update(); +} + +/*! + \reimp +*/ +void QTableView::updateGeometries() +{ + Q_D(QTableView); + if (d->geometryRecursionBlock) + return; + d->geometryRecursionBlock = true; + + int width = 0; + if (!d->verticalHeader->isHidden()) { + width = qMax(d->verticalHeader->minimumWidth(), d->verticalHeader->sizeHint().width()); + width = qMin(width, d->verticalHeader->maximumWidth()); + } + int height = 0; + if (!d->horizontalHeader->isHidden()) { + height = qMax(d->horizontalHeader->minimumHeight(), d->horizontalHeader->sizeHint().height()); + height = qMin(height, d->horizontalHeader->maximumHeight()); + } + bool reverse = isRightToLeft(); + if (reverse) + setViewportMargins(0, height, width, 0); + else + setViewportMargins(width, height, 0, 0); + + // update headers + + QRect vg = d->viewport->geometry(); + + int verticalLeft = reverse ? vg.right() + 1 : (vg.left() - width); + d->verticalHeader->setGeometry(verticalLeft, vg.top(), width, vg.height()); + if (d->verticalHeader->isHidden()) + QMetaObject::invokeMethod(d->verticalHeader, "updateGeometries"); + + int horizontalTop = vg.top() - height; + d->horizontalHeader->setGeometry(vg.left(), horizontalTop, vg.width(), height); + if (d->horizontalHeader->isHidden()) + QMetaObject::invokeMethod(d->horizontalHeader, "updateGeometries"); + + // update cornerWidget + if (d->horizontalHeader->isHidden() || d->verticalHeader->isHidden()) { + d->cornerWidget->setHidden(true); + } else { + d->cornerWidget->setHidden(false); + d->cornerWidget->setGeometry(verticalLeft, horizontalTop, width, height); + } + + // update scroll bars + + // ### move this block into the if + QSize vsize = d->viewport->size(); + QSize max = maximumViewportSize(); + uint horizontalLength = d->horizontalHeader->length(); + uint verticalLength = d->verticalHeader->length(); + if ((uint)max.width() >= horizontalLength && (uint)max.height() >= verticalLength) + vsize = max; + + // horizontal scroll bar + const int columnCount = d->horizontalHeader->count(); + const int viewportWidth = vsize.width(); + int columnsInViewport = 0; + for (int width = 0, column = columnCount - 1; column >= 0; --column) { + int logical = d->horizontalHeader->logicalIndex(column); + if (!d->horizontalHeader->isSectionHidden(logical)) { + width += d->horizontalHeader->sectionSize(logical); + if (width > viewportWidth) + break; + ++columnsInViewport; + } + } + if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) { + const int visibleColumns = columnCount - d->horizontalHeader->hiddenSectionCount(); + horizontalScrollBar()->setRange(0, visibleColumns - columnsInViewport); + horizontalScrollBar()->setPageStep(columnsInViewport); + if (columnsInViewport >= visibleColumns) + d->horizontalHeader->setOffset(0); + horizontalScrollBar()->setSingleStep(1); + } else { // ScrollPerPixel + horizontalScrollBar()->setPageStep(vsize.width()); + horizontalScrollBar()->setRange(0, horizontalLength - vsize.width()); + horizontalScrollBar()->setSingleStep(qMax(vsize.width() / (columnsInViewport + 1), 2)); + } + + // vertical scroll bar + const int rowCount = d->verticalHeader->count(); + const int viewportHeight = vsize.height(); + int rowsInViewport = 0; + for (int height = 0, row = rowCount - 1; row >= 0; --row) { + int logical = d->verticalHeader->logicalIndex(row); + if (!d->verticalHeader->isSectionHidden(logical)) { + height += d->verticalHeader->sectionSize(logical); + if (height > viewportHeight) + break; + ++rowsInViewport; + } + } + if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) { + const int visibleRows = rowCount - d->verticalHeader->hiddenSectionCount(); + verticalScrollBar()->setRange(0, visibleRows - rowsInViewport); + verticalScrollBar()->setPageStep(rowsInViewport); + if (rowsInViewport >= visibleRows) + d->verticalHeader->setOffset(0); + verticalScrollBar()->setSingleStep(1); + } else { // ScrollPerPixel + verticalScrollBar()->setPageStep(vsize.height()); + verticalScrollBar()->setRange(0, verticalLength - vsize.height()); + verticalScrollBar()->setSingleStep(qMax(vsize.height() / (rowsInViewport + 1), 2)); + } + + d->geometryRecursionBlock = false; + QAbstractItemView::updateGeometries(); +} + +/*! + Returns the size hint for the given \a row's height or -1 if there + is no model. + + If you need to set the height of a given row to a fixed value, call + QHeaderView::resizeSection() on the table's vertical header. + + If you reimplement this function in a subclass, note that the value you + return is only used when resizeRowToContents() is called. In that case, + if a larger row height is required by either the vertical header or + the item delegate, that width will be used instead. + + \sa QWidget::sizeHint, verticalHeader() +*/ +int QTableView::sizeHintForRow(int row) const +{ + Q_D(const QTableView); + + if (!model()) + return -1; + + int left = qMax(0, columnAt(0)); + int right = columnAt(d->viewport->width()); + if (right == -1) // the table don't have enough columns to fill the viewport + right = d->model->columnCount(d->root) - 1; + + QStyleOptionViewItemV4 option = d->viewOptionsV4(); + + int hint = 0; + QModelIndex index; + for (int column = left; column <= right; ++column) { + int logicalColumn = d->horizontalHeader->logicalIndex(column); + if (d->horizontalHeader->isSectionHidden(logicalColumn)) + continue; + index = d->model->index(row, logicalColumn, d->root); + if (d->wrapItemText) {// for wrapping boundaries + option.rect.setY(rowViewportPosition(index.row())); + option.rect.setHeight(rowHeight(index.row())); + option.rect.setX(columnViewportPosition(index.column())); + option.rect.setWidth(columnWidth(index.column())); + } + + QWidget *editor = d->editorForIndex(index).editor; + if (editor && d->persistent.contains(editor)) { + hint = qMax(hint, editor->sizeHint().height()); + int min = editor->minimumSize().height(); + int max = editor->maximumSize().height(); + hint = qBound(min, hint, max); + } + + hint = qMax(hint, itemDelegate(index)->sizeHint(option, index).height()); + } + + return d->showGrid ? hint + 1 : hint; +} + +/*! + Returns the size hint for the given \a column's width or -1 if + there is no model. + + If you need to set the width of a given column to a fixed value, call + QHeaderView::resizeSection() on the table's horizontal header. + + If you reimplement this function in a subclass, note that the value you + return will be used when resizeColumnToContents() or + QHeaderView::resizeSections() is called. If a larger column width is + required by either the horizontal header or the item delegate, the larger + width will be used instead. + + \sa QWidget::sizeHint, horizontalHeader() +*/ +int QTableView::sizeHintForColumn(int column) const +{ + Q_D(const QTableView); + + if (!model()) + return -1; + + int top = qMax(0, rowAt(0)); + int bottom = rowAt(d->viewport->height()); + if (!isVisible() || bottom == -1) // the table don't have enough rows to fill the viewport + bottom = d->model->rowCount(d->root) - 1; + + QStyleOptionViewItemV4 option = d->viewOptionsV4(); + + int hint = 0; + QModelIndex index; + for (int row = top; row <= bottom; ++row) { + int logicalRow = d->verticalHeader->logicalIndex(row); + if (d->verticalHeader->isSectionHidden(logicalRow)) + continue; + index = d->model->index(logicalRow, column, d->root); + + QWidget *editor = d->editorForIndex(index).editor; + if (editor && d->persistent.contains(editor)) { + hint = qMax(hint, editor->sizeHint().width()); + int min = editor->minimumSize().width(); + int max = editor->maximumSize().width(); + hint = qBound(min, hint, max); + } + + hint = qMax(hint, itemDelegate(index)->sizeHint(option, index).width()); + } + + return d->showGrid ? hint + 1 : hint; +} + +/*! + Returns the y-coordinate in contents coordinates of the given \a + row. +*/ +int QTableView::rowViewportPosition(int row) const +{ + Q_D(const QTableView); + return d->verticalHeader->sectionViewportPosition(row); +} + +/*! + Returns the row in which the given y-coordinate, \a y, in contents + coordinates is located. + + \note This function returns -1 if the given coordinate is not valid + (has no row). + + \sa columnAt() +*/ +int QTableView::rowAt(int y) const +{ + Q_D(const QTableView); + return d->verticalHeader->logicalIndexAt(y); +} + +/*! + \since 4.1 + + Sets the height of the given \a row to be \a height. +*/ +void QTableView::setRowHeight(int row, int height) +{ + Q_D(const QTableView); + d->verticalHeader->resizeSection(row, height); +} + +/*! + Returns the height of the given \a row. + + \sa resizeRowToContents(), columnWidth() +*/ +int QTableView::rowHeight(int row) const +{ + Q_D(const QTableView); + return d->verticalHeader->sectionSize(row); +} + +/*! + Returns the x-coordinate in contents coordinates of the given \a + column. +*/ +int QTableView::columnViewportPosition(int column) const +{ + Q_D(const QTableView); + return d->horizontalHeader->sectionViewportPosition(column); +} + +/*! + Returns the column in which the given x-coordinate, \a x, in contents + coordinates is located. + + \note This function returns -1 if the given coordinate is not valid + (has no column). + + \sa rowAt() +*/ +int QTableView::columnAt(int x) const +{ + Q_D(const QTableView); + return d->horizontalHeader->logicalIndexAt(x); +} + +/*! + \since 4.1 + + Sets the width of the given \a column to be \a width. +*/ +void QTableView::setColumnWidth(int column, int width) +{ + Q_D(const QTableView); + d->horizontalHeader->resizeSection(column, width); +} + +/*! + Returns the width of the given \a column. + + \sa resizeColumnToContents(), rowHeight() +*/ +int QTableView::columnWidth(int column) const +{ + Q_D(const QTableView); + return d->horizontalHeader->sectionSize(column); +} + +/*! + Returns true if the given \a row is hidden; otherwise returns false. + + \sa isColumnHidden() +*/ +bool QTableView::isRowHidden(int row) const +{ + Q_D(const QTableView); + return d->verticalHeader->isSectionHidden(row); +} + +/*! + If \a hide is true \a row will be hidden, otherwise it will be shown. + + \sa setColumnHidden() +*/ +void QTableView::setRowHidden(int row, bool hide) +{ + Q_D(QTableView); + if (row < 0 || row >= d->verticalHeader->count()) + return; + d->verticalHeader->setSectionHidden(row, hide); +} + +/*! + Returns true if the given \a column is hidden; otherwise returns false. + + \sa isRowHidden() +*/ +bool QTableView::isColumnHidden(int column) const +{ + Q_D(const QTableView); + return d->horizontalHeader->isSectionHidden(column); +} + +/*! + If \a hide is true the given \a column will be hidden; otherwise it + will be shown. + + \sa setRowHidden() +*/ +void QTableView::setColumnHidden(int column, bool hide) +{ + Q_D(QTableView); + if (column < 0 || column >= d->horizontalHeader->count()) + return; + d->horizontalHeader->setSectionHidden(column, hide); +} + +/*! + \since 4.2 + \property QTableView::sortingEnabled + \brief whether sorting is enabled + + If this property is true, sorting is enabled for the table; if the + property is false, sorting is not enabled. The default value is false. + + \sa sortByColumn() +*/ + +void QTableView::setSortingEnabled(bool enable) +{ + Q_D(QTableView); + d->sortingEnabled = enable; + horizontalHeader()->setSortIndicatorShown(enable); + if (enable) { + disconnect(d->horizontalHeader, SIGNAL(sectionEntered(int)), + this, SLOT(_q_selectColumn(int))); + disconnect(horizontalHeader(), SIGNAL(sectionPressed(int)), + this, SLOT(selectColumn(int))); + connect(horizontalHeader(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), + this, SLOT(sortByColumn(int))); + sortByColumn(horizontalHeader()->sortIndicatorSection(), + horizontalHeader()->sortIndicatorOrder()); + } else { + connect(d->horizontalHeader, SIGNAL(sectionEntered(int)), + this, SLOT(_q_selectColumn(int))); + connect(horizontalHeader(), SIGNAL(sectionPressed(int)), + this, SLOT(selectColumn(int))); + disconnect(horizontalHeader(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), + this, SLOT(sortByColumn(int))); + } +} + +bool QTableView::isSortingEnabled() const +{ + Q_D(const QTableView); + return d->sortingEnabled; +} + +/*! + \property QTableView::showGrid + \brief whether the grid is shown + + If this property is true a grid is drawn for the table; if the + property is false, no grid is drawn. The default value is true. +*/ +bool QTableView::showGrid() const +{ + Q_D(const QTableView); + return d->showGrid; +} + +void QTableView::setShowGrid(bool show) +{ + Q_D(QTableView); + if (d->showGrid != show) { + d->showGrid = show; + d->viewport->update(); + } +} + +/*! + \property QTableView::gridStyle + \brief the pen style used to draw the grid. + + This property holds the style used when drawing the grid (see \l{showGrid}). +*/ +Qt::PenStyle QTableView::gridStyle() const +{ + Q_D(const QTableView); + return d->gridStyle; +} + +void QTableView::setGridStyle(Qt::PenStyle style) +{ + Q_D(QTableView); + if (d->gridStyle != style) { + d->gridStyle = style; + d->viewport->update(); + } +} + +/*! + \property QTableView::wordWrap + \brief the item text word-wrapping policy + \since 4.3 + + If this property is true then the item text is wrapped where + necessary at word-breaks; otherwise it is not wrapped at all. + This property is true by default. + + Note that even of wrapping is enabled, the cell will not be + expanded to fit all text. Ellipsis will be inserted according to + the current \l{QAbstractItemView::}{textElideMode}. + +*/ +void QTableView::setWordWrap(bool on) +{ + Q_D(QTableView); + if (d->wrapItemText == on) + return; + d->wrapItemText = on; + QMetaObject::invokeMethod(d->verticalHeader, "resizeSections"); + QMetaObject::invokeMethod(d->horizontalHeader, "resizeSections"); +} + +bool QTableView::wordWrap() const +{ + Q_D(const QTableView); + return d->wrapItemText; +} + +/*! + \property QTableView::cornerButtonEnabled + \brief whether the button in the top-left corner is enabled + \since 4.3 + + If this property is true then button in the top-left corner + of the table view is enabled. Clicking on this button will + select all the cells in the table view. + + This property is true by default. +*/ +void QTableView::setCornerButtonEnabled(bool enable) +{ + Q_D(QTableView); + d->cornerWidget->setEnabled(enable); +} + +bool QTableView::isCornerButtonEnabled() const +{ + Q_D(const QTableView); + return d->cornerWidget->isEnabled(); +} + +/*! + \internal + + Returns the rectangle on the viewport occupied by the given \a + index. + If the index is hidden in the view it will return a null QRect. +*/ +QRect QTableView::visualRect(const QModelIndex &index) const +{ + Q_D(const QTableView); + if (!d->isIndexValid(index) || index.parent() != d->root || isIndexHidden(index) ) + return QRect(); + + d->executePostedLayout(); + + if (d->hasSpans()) { + QTableViewPrivate::Span span = d->span(index.row(), index.column()); + return d->visualSpanRect(span); + } + + int rowp = rowViewportPosition(index.row()); + int rowh = rowHeight(index.row()); + int colp = columnViewportPosition(index.column()); + int colw = columnWidth(index.column()); + + const int i = showGrid() ? 1 : 0; + return QRect(colp, rowp, colw - i, rowh - i); +} + +/*! + \internal + + Makes sure that the given \a item is visible in the table view, + scrolling if necessary. +*/ +void QTableView::scrollTo(const QModelIndex &index, ScrollHint hint) +{ + Q_D(QTableView); + + // check if we really need to do anything + if (!d->isIndexValid(index) + || (d->model->parent(index) != d->root) + || isIndexHidden(index)) + return; + + QTableViewPrivate::Span span; + if (d->hasSpans()) + span = d->span(index.row(), index.column()); + + // Adjust horizontal position + + int viewportWidth = d->viewport->width(); + int horizontalOffset = d->horizontalHeader->offset(); + int horizontalPosition = d->horizontalHeader->sectionPosition(index.column()); + int horizontalIndex = d->horizontalHeader->visualIndex(index.column()); + int cellWidth = d->hasSpans() + ? d->columnSpanWidth(index.column(), span.width()) + : d->horizontalHeader->sectionSize(index.column()); + + if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) { + + bool positionAtLeft = (horizontalPosition - horizontalOffset < 0); + bool positionAtRight = (horizontalPosition - horizontalOffset + cellWidth > viewportWidth); + + if (hint == PositionAtCenter || positionAtRight) { + int w = (hint == PositionAtCenter ? viewportWidth / 2 : viewportWidth); + int x = cellWidth; + while (horizontalIndex > 0) { + x += columnWidth(d->horizontalHeader->logicalIndex(horizontalIndex-1)); + if (x > w) + break; + --horizontalIndex; + } + } + + if (positionAtRight || hint == PositionAtCenter || positionAtLeft) { + int hiddenSections = 0; + if (d->horizontalHeader->sectionsHidden()) { + for (int s = horizontalIndex; s >= 0; --s) { + int column = d->horizontalHeader->logicalIndex(s); + if (d->horizontalHeader->isSectionHidden(column)) + ++hiddenSections; + } + } + horizontalScrollBar()->setValue(horizontalIndex - hiddenSections); + } + + } else { // ScrollPerPixel + if (hint == PositionAtCenter) { + horizontalScrollBar()->setValue(horizontalPosition - ((viewportWidth - cellWidth) / 2)); + } else { + if (horizontalPosition - horizontalOffset < 0 || cellWidth > viewportWidth) + horizontalScrollBar()->setValue(horizontalPosition); + else if (horizontalPosition - horizontalOffset + cellWidth > viewportWidth) + horizontalScrollBar()->setValue(horizontalPosition - viewportWidth + cellWidth); + } + } + + // Adjust vertical position + + int viewportHeight = d->viewport->height(); + int verticalOffset = d->verticalHeader->offset(); + int verticalPosition = d->verticalHeader->sectionPosition(index.row()); + int verticalIndex = d->verticalHeader->visualIndex(index.row()); + int cellHeight = d->hasSpans() + ? d->rowSpanHeight(index.row(), span.height()) + : d->verticalHeader->sectionSize(index.row()); + + if (verticalPosition - verticalOffset < 0 || cellHeight > viewportHeight) { + if (hint == EnsureVisible) + hint = PositionAtTop; + } else if (verticalPosition - verticalOffset + cellHeight > viewportHeight) { + if (hint == EnsureVisible) + hint = PositionAtBottom; + } + + if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) { + + if (hint == PositionAtBottom || hint == PositionAtCenter) { + int h = (hint == PositionAtCenter ? viewportHeight / 2 : viewportHeight); + int y = cellHeight; + while (verticalIndex > 0) { + int row = d->verticalHeader->logicalIndex(verticalIndex - 1); + y += d->verticalHeader->sectionSize(row); + if (y > h) + break; + --verticalIndex; + } + } + + if (hint == PositionAtBottom || hint == PositionAtCenter || hint == PositionAtTop) { + int hiddenSections = 0; + if (d->verticalHeader->sectionsHidden()) { + for (int s = verticalIndex; s >= 0; --s) { + int row = d->verticalHeader->logicalIndex(s); + if (d->verticalHeader->isSectionHidden(row)) + ++hiddenSections; + } + } + verticalScrollBar()->setValue(verticalIndex - hiddenSections); + } + + } else { // ScrollPerPixel + if (hint == PositionAtTop) { + verticalScrollBar()->setValue(verticalPosition); + } else if (hint == PositionAtBottom) { + verticalScrollBar()->setValue(verticalPosition - viewportHeight + cellHeight); + } else if (hint == PositionAtCenter) { + verticalScrollBar()->setValue(verticalPosition - ((viewportHeight - cellHeight) / 2)); + } + } + + d->setDirtyRegion(visualRect(index)); +} + +/*! + This slot is called to change the height of the given \a row. The + old height is specified by \a oldHeight, and the new height by \a + newHeight. + + \sa columnResized() +*/ +void QTableView::rowResized(int row, int, int) +{ + Q_D(QTableView); + d->rowsToUpdate.append(row); + if (d->rowResizeTimerID == 0) + d->rowResizeTimerID = startTimer(0); +} + +/*! + This slot is called to change the width of the given \a column. + The old width is specified by \a oldWidth, and the new width by \a + newWidth. + + \sa rowResized() +*/ +void QTableView::columnResized(int column, int, int) +{ + Q_D(QTableView); + d->columnsToUpdate.append(column); + if (d->columnResizeTimerID == 0) + d->columnResizeTimerID = startTimer(0); +} + +/*! + \reimp + */ +void QTableView::timerEvent(QTimerEvent *event) +{ + Q_D(QTableView); + + if (event->timerId() == d->columnResizeTimerID) { + updateGeometries(); + killTimer(d->columnResizeTimerID); + d->columnResizeTimerID = 0; + + QRect rect; + int viewportHeight = d->viewport->height(); + int viewportWidth = d->viewport->width(); + if (d->hasSpans() && d->spansIntersectColumns(d->columnsToUpdate)) { + rect = QRect(0, 0, viewportWidth, viewportHeight); + } else { + for (int i = d->columnsToUpdate.size()-1; i >= 0; --i) { + int column = d->columnsToUpdate.at(i); + int x = columnViewportPosition(column); + if (isRightToLeft()) + rect |= QRect(0, 0, x + columnWidth(column), viewportHeight); + else + rect |= QRect(x, 0, viewportWidth - x, viewportHeight); + } + } + + d->viewport->update(rect.normalized()); + d->columnsToUpdate.clear(); + } + + if (event->timerId() == d->rowResizeTimerID) { + updateGeometries(); + killTimer(d->rowResizeTimerID); + d->rowResizeTimerID = 0; + + int viewportHeight = d->viewport->height(); + int viewportWidth = d->viewport->width(); + int top; + if (d->hasSpans() && d->spansIntersectRows(d->rowsToUpdate)) { + top = 0; + } else { + top = viewportHeight; + for (int i = d->rowsToUpdate.size()-1; i >= 0; --i) { + int y = rowViewportPosition(d->rowsToUpdate.at(i)); + top = qMin(top, y); + } + } + + d->viewport->update(QRect(0, top, viewportWidth, viewportHeight - top)); + d->rowsToUpdate.clear(); + } + + QAbstractItemView::timerEvent(event); +} + +/*! + This slot is called to change the index of the given \a row in the + table view. The old index is specified by \a oldIndex, and the new + index by \a newIndex. + + \sa columnMoved() +*/ +void QTableView::rowMoved(int, int oldIndex, int newIndex) +{ + Q_D(QTableView); + + updateGeometries(); + int logicalOldIndex = d->verticalHeader->logicalIndex(oldIndex); + int logicalNewIndex = d->verticalHeader->logicalIndex(newIndex); + if (d->hasSpans() && (d->spansIntersectRow(logicalOldIndex) || d->spansIntersectRow(logicalNewIndex))) { + d->viewport->update(); + } else { + int oldTop = rowViewportPosition(logicalOldIndex); + int newTop = rowViewportPosition(logicalNewIndex); + int oldBottom = oldTop + rowHeight(logicalOldIndex); + int newBottom = newTop + rowHeight(logicalNewIndex); + int top = qMin(oldTop, newTop); + int bottom = qMax(oldBottom, newBottom); + int height = bottom - top; + d->viewport->update(0, top, d->viewport->width(), height); + } +} + +/*! + This slot is called to change the index of the given \a column in + the table view. The old index is specified by \a oldIndex, and + the new index by \a newIndex. + + \sa rowMoved() +*/ +void QTableView::columnMoved(int, int oldIndex, int newIndex) +{ + Q_D(QTableView); + + updateGeometries(); + int logicalOldIndex = d->horizontalHeader->logicalIndex(oldIndex); + int logicalNewIndex = d->horizontalHeader->logicalIndex(newIndex); + if (d->hasSpans() && (d->spansIntersectColumn(logicalOldIndex) || d->spansIntersectColumn(logicalNewIndex))) { + d->viewport->update(); + } else { + int oldLeft = columnViewportPosition(logicalOldIndex); + int newLeft = columnViewportPosition(logicalNewIndex); + int oldRight = oldLeft + columnWidth(logicalOldIndex); + int newRight = newLeft + columnWidth(logicalNewIndex); + int left = qMin(oldLeft, newLeft); + int right = qMax(oldRight, newRight); + int width = right - left; + d->viewport->update(left, 0, width, d->viewport->height()); + } +} + +/*! + Selects the given \a row in the table view if the current + SelectionMode and SelectionBehavior allows rows to be selected. + + \sa selectColumn() +*/ +void QTableView::selectRow(int row) +{ + Q_D(QTableView); + d->selectRow(row, true); +} + +/*! + Selects the given \a column in the table view if the current + SelectionMode and SelectionBehavior allows columns to be selected. + + \sa selectRow() +*/ +void QTableView::selectColumn(int column) +{ + Q_D(QTableView); + d->selectColumn(column, true); +} + +/*! + Hide the given \a row. + + \sa showRow() hideColumn() +*/ +void QTableView::hideRow(int row) +{ + Q_D(QTableView); + d->verticalHeader->hideSection(row); +} + +/*! + Hide the given \a column. + + \sa showColumn() hideRow() +*/ +void QTableView::hideColumn(int column) +{ + Q_D(QTableView); + d->horizontalHeader->hideSection(column); +} + +/*! + Show the given \a row. + + \sa hideRow() showColumn() +*/ +void QTableView::showRow(int row) +{ + Q_D(QTableView); + d->verticalHeader->showSection(row); +} + +/*! + Show the given \a column. + + \sa hideColumn() showRow() +*/ +void QTableView::showColumn(int column) +{ + Q_D(QTableView); + d->horizontalHeader->showSection(column); +} + +/*! + Resizes the given \a row based on the size hints of the delegate + used to render each item in the row. +*/ +void QTableView::resizeRowToContents(int row) +{ + Q_D(QTableView); + int content = sizeHintForRow(row); + int header = d->verticalHeader->sectionSizeHint(row); + d->verticalHeader->resizeSection(row, qMax(content, header)); +} + +/*! + Resizes all rows based on the size hints of the delegate + used to render each item in the rows. +*/ +void QTableView::resizeRowsToContents() +{ + Q_D(QTableView); + d->verticalHeader->resizeSections(QHeaderView::ResizeToContents); +} + +/*! + Resizes the given \a column based on the size hints of the delegate + used to render each item in the column. + + \note Only visible columns will be resized. Reimplement sizeHintForColumn() + to resize hidden columns as well. +*/ +void QTableView::resizeColumnToContents(int column) +{ + Q_D(QTableView); + int content = sizeHintForColumn(column); + int header = d->horizontalHeader->sectionSizeHint(column); + d->horizontalHeader->resizeSection(column, qMax(content, header)); +} + +/*! + Resizes all columns based on the size hints of the delegate + used to render each item in the columns. +*/ +void QTableView::resizeColumnsToContents() +{ + Q_D(QTableView); + d->horizontalHeader->resizeSections(QHeaderView::ResizeToContents); +} + +/*! + \obsolete + \overload + + Sorts the model by the values in the given \a column. +*/ +void QTableView::sortByColumn(int column) +{ + Q_D(QTableView); + if (column == -1) + return; + d->model->sort(column, d->horizontalHeader->sortIndicatorOrder()); +} + +/*! + \since 4.2 + + Sorts the model by the values in the given \a column in the given \a order. + + \sa sortingEnabled + */ +void QTableView::sortByColumn(int column, Qt::SortOrder order) +{ + Q_D(QTableView); + d->horizontalHeader->setSortIndicator(column, order); + sortByColumn(column); +} + +/*! + \internal +*/ +void QTableView::verticalScrollbarAction(int action) +{ + QAbstractItemView::verticalScrollbarAction(action); +} + +/*! + \internal +*/ +void QTableView::horizontalScrollbarAction(int action) +{ + QAbstractItemView::horizontalScrollbarAction(action); +} + +/*! + \reimp +*/ +bool QTableView::isIndexHidden(const QModelIndex &index) const +{ + Q_D(const QTableView); + Q_ASSERT(d->isIndexValid(index)); + if (isRowHidden(index.row()) || isColumnHidden(index.column())) + return true; + if (d->hasSpans()) { + QTableViewPrivate::Span span = d->span(index.row(), index.column()); + return !((span.top() == index.row()) && (span.left() == index.column())); + } + return false; +} + +/*! + \fn void QTableView::setSpan(int row, int column, int rowSpanCount, int columnSpanCount) + \since 4.2 + + Sets the span of the table element at (\a row, \a column) to the number of + rows and columns specified by (\a rowSpanCount, \a columnSpanCount). + + \sa rowSpan(), columnSpan() +*/ +void QTableView::setSpan(int row, int column, int rowSpan, int columnSpan) +{ + Q_D(QTableView); + if (row < 0 || column < 0 || rowSpan < 0 || columnSpan < 0) + return; + d->setSpan(row, column, rowSpan, columnSpan); + d->viewport->update(); +} + +/*! + \since 4.2 + + Returns the row span of the table element at (\a row, \a column). + The default is 1. + + \sa setSpan(), columnSpan() +*/ +int QTableView::rowSpan(int row, int column) const +{ + Q_D(const QTableView); + return d->rowSpan(row, column); +} + +/*! + \since 4.2 + + Returns the column span of the table element at (\a row, \a + column). The default is 1. + + \sa setSpan(), rowSpan() +*/ +int QTableView::columnSpan(int row, int column) const +{ + Q_D(const QTableView); + return d->columnSpan(row, column); +} + +/*! + \since 4.4 + + Removes all row and column spans in the table view. + + \sa setSpan() +*/ + +void QTableView::clearSpans() +{ + Q_D(QTableView); + d->spans.clear(); + d->viewport->update(); +} + +void QTableViewPrivate::_q_selectRow(int row) +{ + selectRow(row, false); +} + +void QTableViewPrivate::_q_selectColumn(int column) +{ + selectColumn(column, false); +} + +void QTableViewPrivate::selectRow(int row, bool anchor) +{ + Q_Q(QTableView); + + if (q->selectionBehavior() == QTableView::SelectColumns + || (q->selectionMode() == QTableView::SingleSelection + && q->selectionBehavior() == QTableView::SelectItems)) + return; + + if (row >= 0 && row < model->rowCount(root)) { + int column = horizontalHeader->logicalIndexAt(q->isRightToLeft() ? viewport->width() : 0); + QModelIndex index = model->index(row, column, root); + QItemSelectionModel::SelectionFlags command = q->selectionCommand(index); + selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + if ((!(command & QItemSelectionModel::Current) && anchor) + || (q->selectionMode() == QTableView::SingleSelection)) + rowSectionAnchor = row; + QModelIndex tl = model->index(qMin(rowSectionAnchor, row), 0, root); + QModelIndex br = model->index(qMax(rowSectionAnchor, row), model->columnCount(root) - 1, root); + if (verticalHeader->sectionsMoved() && tl.row() != br.row()) + q->setSelection(q->visualRect(tl)|q->visualRect(br), command); + else + selectionModel->select(QItemSelection(tl, br), command); + } +} + +void QTableViewPrivate::selectColumn(int column, bool anchor) +{ + Q_Q(QTableView); + + if (q->selectionBehavior() == QTableView::SelectRows + || (q->selectionMode() == QTableView::SingleSelection + && q->selectionBehavior() == QTableView::SelectItems)) + return; + + if (column >= 0 && column < model->columnCount(root)) { + int row = verticalHeader->logicalIndexAt(0); + QModelIndex index = model->index(row, column, root); + QItemSelectionModel::SelectionFlags command = q->selectionCommand(index); + selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + if ((!(command & QItemSelectionModel::Current) && anchor) + || (q->selectionMode() == QTableView::SingleSelection)) + columnSectionAnchor = column; + QModelIndex tl = model->index(0, qMin(columnSectionAnchor, column), root); + QModelIndex br = model->index(model->rowCount(root) - 1, + qMax(columnSectionAnchor, column), root); + if (horizontalHeader->sectionsMoved() && tl.column() != br.column()) + q->setSelection(q->visualRect(tl)|q->visualRect(br), command); + else + selectionModel->select(QItemSelection(tl, br), command); + } +} + +/*! + \reimp + */ +void QTableView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ +#ifndef QT_NO_ACCESSIBILITY + if (QAccessible::isActive()) { + if (current.isValid()) { + int entry = visualIndex(current) + 1; + if (horizontalHeader()) + ++entry; + QAccessible::updateAccessibility(viewport(), entry, QAccessible::Focus); + } + } +#endif + QAbstractItemView::currentChanged(current, previous); +} + +/*! + \reimp + */ +void QTableView::selectionChanged(const QItemSelection &selected, + const QItemSelection &deselected) +{ +#ifndef QT_NO_ACCESSIBILITY + if (QAccessible::isActive()) { + // ### does not work properly for selection ranges. + QModelIndex sel = selected.indexes().value(0); + if (sel.isValid()) { + int entry = visualIndex(sel); + if (horizontalHeader()) + ++entry; + QAccessible::updateAccessibility(viewport(), entry, QAccessible::Selection); + } + QModelIndex desel = deselected.indexes().value(0); + if (desel.isValid()) { + int entry = visualIndex(sel); + if (horizontalHeader()) + ++entry; + QAccessible::updateAccessibility(viewport(), entry, QAccessible::SelectionRemove); + } + } +#endif + QAbstractItemView::selectionChanged(selected, deselected); +} + +int QTableView::visualIndex(const QModelIndex &index) const +{ + return index.row(); +} + +QT_END_NAMESPACE + +#include "qtableview.moc" + +#include "moc_qtableview.cpp" + +#endif // QT_NO_TABLEVIEW diff --git a/src/gui/itemviews/qtableview.h b/src/gui/itemviews/qtableview.h new file mode 100644 index 0000000..6654d7f --- /dev/null +++ b/src/gui/itemviews/qtableview.h @@ -0,0 +1,193 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTABLEVIEW_H +#define QTABLEVIEW_H + +#include <QtGui/qabstractitemview.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_TABLEVIEW + +class QHeaderView; +class QTableViewPrivate; + +class Q_GUI_EXPORT QTableView : public QAbstractItemView +{ + Q_OBJECT + Q_PROPERTY(bool showGrid READ showGrid WRITE setShowGrid) + Q_PROPERTY(Qt::PenStyle gridStyle READ gridStyle WRITE setGridStyle) + Q_PROPERTY(bool sortingEnabled READ isSortingEnabled WRITE setSortingEnabled) + Q_PROPERTY(bool wordWrap READ wordWrap WRITE setWordWrap) + Q_PROPERTY(bool cornerButtonEnabled READ isCornerButtonEnabled WRITE setCornerButtonEnabled) + +public: + explicit QTableView(QWidget *parent = 0); + ~QTableView(); + + void setModel(QAbstractItemModel *model); + void setRootIndex(const QModelIndex &index); + void setSelectionModel(QItemSelectionModel *selectionModel); + + QHeaderView *horizontalHeader() const; + QHeaderView *verticalHeader() const; + void setHorizontalHeader(QHeaderView *header); + void setVerticalHeader(QHeaderView *header); + + int rowViewportPosition(int row) const; + int rowAt(int y) const; + + void setRowHeight(int row, int height); + int rowHeight(int row) const; + + int columnViewportPosition(int column) const; + int columnAt(int x) const; + + void setColumnWidth(int column, int width); + int columnWidth(int column) const; + + bool isRowHidden(int row) const; + void setRowHidden(int row, bool hide); + + bool isColumnHidden(int column) const; + void setColumnHidden(int column, bool hide); + + void setSortingEnabled(bool enable); + bool isSortingEnabled() const; + + bool showGrid() const; + + Qt::PenStyle gridStyle() const; + void setGridStyle(Qt::PenStyle style); + + void setWordWrap(bool on); + bool wordWrap() const; + + void setCornerButtonEnabled(bool enable); + bool isCornerButtonEnabled() const; + + QRect visualRect(const QModelIndex &index) const; + void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible); + QModelIndex indexAt(const QPoint &p) const; + + void setSpan(int row, int column, int rowSpan, int columnSpan); + int rowSpan(int row, int column) const; + int columnSpan(int row, int column) const; + void clearSpans(); + + void sortByColumn(int column, Qt::SortOrder order); + +public Q_SLOTS: + void selectRow(int row); + void selectColumn(int column); + void hideRow(int row); + void hideColumn(int column); + void showRow(int row); + void showColumn(int column); + void resizeRowToContents(int row); + void resizeRowsToContents(); + void resizeColumnToContents(int column); + void resizeColumnsToContents(); + void sortByColumn(int column); + void setShowGrid(bool show); + +protected Q_SLOTS: + void rowMoved(int row, int oldIndex, int newIndex); + void columnMoved(int column, int oldIndex, int newIndex); + void rowResized(int row, int oldHeight, int newHeight); + void columnResized(int column, int oldWidth, int newWidth); + void rowCountChanged(int oldCount, int newCount); + void columnCountChanged(int oldCount, int newCount); + +protected: + QTableView(QTableViewPrivate &, QWidget *parent); + void scrollContentsBy(int dx, int dy); + + QStyleOptionViewItem viewOptions() const; + void paintEvent(QPaintEvent *e); + + void timerEvent(QTimerEvent *event); + + int horizontalOffset() const; + int verticalOffset() const; + QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers); + + void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command); + QRegion visualRegionForSelection(const QItemSelection &selection) const; + QModelIndexList selectedIndexes() const; + + void updateGeometries(); + + int sizeHintForRow(int row) const; + int sizeHintForColumn(int column) const; + + void verticalScrollbarAction(int action); + void horizontalScrollbarAction(int action); + + bool isIndexHidden(const QModelIndex &index) const; + + void selectionChanged(const QItemSelection &selected, + const QItemSelection &deselected); + void currentChanged(const QModelIndex ¤t, + const QModelIndex &previous); + +private: + friend class QAccessibleItemView; + int visualIndex(const QModelIndex &index) const; + + Q_DECLARE_PRIVATE(QTableView) + Q_DISABLE_COPY(QTableView) + Q_PRIVATE_SLOT(d_func(), void _q_selectRow(int)) + Q_PRIVATE_SLOT(d_func(), void _q_selectColumn(int)) +}; + +#endif // QT_NO_TABLEVIEW + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTABLEVIEW_H diff --git a/src/gui/itemviews/qtableview_p.h b/src/gui/itemviews/qtableview_p.h new file mode 100644 index 0000000..a1c1152 --- /dev/null +++ b/src/gui/itemviews/qtableview_p.h @@ -0,0 +1,209 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTABLEVIEW_P_H +#define QTABLEVIEW_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qabstractitemview_p.h" + +#ifndef QT_NO_TABLEVIEW + +QT_BEGIN_NAMESPACE + +class QTableViewPrivate : public QAbstractItemViewPrivate +{ + Q_DECLARE_PUBLIC(QTableView) +public: + QTableViewPrivate() + : showGrid(true), gridStyle(Qt::SolidLine), + rowSectionAnchor(-1), columnSectionAnchor(-1), + columnResizeTimerID(0), rowResizeTimerID(0), + horizontalHeader(0), verticalHeader(0), + sortingEnabled(false), geometryRecursionBlock(false) + { + wrapItemText = true; +#ifndef QT_NO_DRAGANDDROP + overwrite = true; +#endif + } + void init(); + void trimHiddenSelections(QItemSelectionRange *range) const; + + inline bool isHidden(int row, int col) const { + return verticalHeader->isSectionHidden(row) + || horizontalHeader->isSectionHidden(col); + } + inline int visualRow(int logicalRow) const { + return verticalHeader->visualIndex(logicalRow); + } + inline int visualColumn(int logicalCol) const { + return horizontalHeader->visualIndex(logicalCol); + } + inline int logicalRow(int visualRow) const { + return verticalHeader->logicalIndex(visualRow); + } + inline int logicalColumn(int visualCol) const { + return horizontalHeader->logicalIndex(visualCol); + } + + int sectionSpanEndLogical(const QHeaderView *header, int logical, int span) const; + int sectionSpanSize(const QHeaderView *header, int logical, int span) const; + bool spanContainsSection(const QHeaderView *header, int logical, int spanLogical, int span) const; + bool spansIntersectColumn(int column) const; + bool spansIntersectRow(int row) const; + bool spansIntersectColumns(const QList<int> &columns) const; + bool spansIntersectRows(const QList<int> &rows) const; + void drawAndClipSpans(const QRect &area, QPainter *painter, + const QStyleOptionViewItemV4 &option, QBitArray *drawn); + void drawCell(QPainter *painter, const QStyleOptionViewItemV4 &option, const QModelIndex &index); + + bool showGrid; + Qt::PenStyle gridStyle; + int rowSectionAnchor; + int columnSectionAnchor; + int columnResizeTimerID; + int rowResizeTimerID; + QList<int> columnsToUpdate; + QList<int> rowsToUpdate; + QHeaderView *horizontalHeader; + QHeaderView *verticalHeader; + QWidget *cornerWidget; + bool sortingEnabled; + bool geometryRecursionBlock; + + struct Span + { + int m_top; + int m_left; + int m_bottom; + int m_right; + Span() + : m_top(-1), m_left(-1), m_bottom(-1), m_right(-1) { } + Span(int row, int column, int rowCount, int columnCount) + : m_top(row), m_left(column), m_bottom(row+rowCount-1), m_right(column+columnCount-1) { } + inline int top() const { return m_top; } + inline int left() const { return m_left; } + inline int bottom() const { return m_bottom; } + inline int right() const { return m_right; } + inline int height() const { return m_bottom - m_top + 1; } + inline int width() const { return m_right - m_left + 1; } + }; + QList<Span> spans; + + void setSpan(int row, int column, int rowSpan, int columnSpan); + Span span(int row, int column) const; + inline int rowSpan(int row, int column) const { + return span(row, column).height(); + } + inline int columnSpan(int row, int column) const { + return span(row, column).width(); + } + inline bool hasSpans() const { + return !spans.isEmpty(); + } + inline bool spanContainsRow(int row, int spanRow, int span) const { + return spanContainsSection(verticalHeader, row, spanRow, span); + } + inline bool spanContainsColumn(int column, int spanColumn, int span) const { + return spanContainsSection(horizontalHeader, column, spanColumn, span); + } + inline bool isInSpan(int row, int column, const Span &span) const { + return spanContainsRow(row, span.top(), span.height()) + && spanContainsColumn(column, span.left(), span.width()); + } + inline int rowSpanHeight(int row, int span) const { + return sectionSpanSize(verticalHeader, row, span); + } + inline int columnSpanWidth(int column, int span) const { + return sectionSpanSize(horizontalHeader, column, span); + } + inline int rowSpanEndLogical(int row, int span) const { + return sectionSpanEndLogical(verticalHeader, row, span); + } + inline int columnSpanEndLogical(int column, int span) const { + return sectionSpanEndLogical(horizontalHeader, column, span); + } + + inline bool isRowHidden(int row) const { + return verticalHeader->isSectionHidden(row); + } + inline bool isColumnHidden(int column) const { + return horizontalHeader->isSectionHidden(column); + } + inline bool isCellEnabled(int row, int column) const { + return isIndexEnabled(model->index(row, column, root)); + } + inline bool isVisualRowHiddenOrDisabled(int row, int column) const { + int r = logicalRow(row); + int c = logicalColumn(column); + return isRowHidden(r) || !isCellEnabled(r, c); + } + inline bool isVisualColumnHiddenOrDisabled(int row, int column) const { + int r = logicalRow(row); + int c = logicalColumn(column); + return isColumnHidden(c) || !isCellEnabled(r, c); + } + + QRect visualSpanRect(const Span &span) const; + + void _q_selectRow(int row); + void _q_selectColumn(int column); + + void selectRow(int row, bool anchor); + void selectColumn(int column, bool anchor); +}; + +QT_END_NAMESPACE + +#endif // QT_NO_TABLEVIEW + +#endif // QTABLEVIEW_P_H diff --git a/src/gui/itemviews/qtablewidget.cpp b/src/gui/itemviews/qtablewidget.cpp new file mode 100644 index 0000000..e88301e --- /dev/null +++ b/src/gui/itemviews/qtablewidget.cpp @@ -0,0 +1,2703 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtablewidget.h" + +#ifndef QT_NO_TABLEWIDGET +#include <qitemdelegate.h> +#include <qpainter.h> +#include <private/qtablewidget_p.h> + +QT_BEGIN_NAMESPACE + +QTableModel::QTableModel(int rows, int columns, QTableWidget *parent) + : QAbstractTableModel(parent), + prototype(0), + tableItems(rows * columns, 0), + verticalHeaderItems(rows, 0), + horizontalHeaderItems(columns, 0) +{} + +QTableModel::~QTableModel() +{ + clear(); + delete prototype; +} + +bool QTableModel::insertRows(int row, int count, const QModelIndex &) +{ + if (count < 1 || row < 0 || row > verticalHeaderItems.count()) + return false; + + beginInsertRows(QModelIndex(), row, row + count - 1); + int rc = verticalHeaderItems.count(); + int cc = horizontalHeaderItems.count(); + verticalHeaderItems.insert(row, count, 0); + if (rc == 0) + tableItems.resize(cc * count); + else + tableItems.insert(tableIndex(row, 0), cc * count, 0); + endInsertRows(); + return true; +} + +bool QTableModel::insertColumns(int column, int count, const QModelIndex &) +{ + if (count < 1 || column < 0 || column > horizontalHeaderItems.count()) + return false; + + beginInsertColumns(QModelIndex(), column, column + count - 1); + int rc = verticalHeaderItems.count(); + int cc = horizontalHeaderItems.count(); + horizontalHeaderItems.insert(column, count, 0); + if (cc == 0) + tableItems.resize(rc * count); + else + for (int row = 0; row < rc; ++row) + tableItems.insert(tableIndex(row, column), count, 0); + endInsertColumns(); + return true; +} + +bool QTableModel::removeRows(int row, int count, const QModelIndex &) +{ + if (count < 1 || row < 0 || row + count > verticalHeaderItems.count()) + return false; + + beginRemoveRows(QModelIndex(), row, row + count - 1); + int i = tableIndex(row, 0); + int n = count * columnCount(); + QTableWidgetItem *oldItem = 0; + for (int j = i; j < n + i; ++j) { + oldItem = tableItems.at(j); + if (oldItem) + oldItem->view = 0; + delete oldItem; + } + tableItems.remove(qMax(i, 0), n); + for (int v = row; v < row + count; ++v) { + oldItem = verticalHeaderItems.at(v); + if (oldItem) + oldItem->view = 0; + delete oldItem; + } + verticalHeaderItems.remove(row, count); + endRemoveRows(); + return true; +} + +bool QTableModel::removeColumns(int column, int count, const QModelIndex &) +{ + if (count < 1 || column < 0 || column + count > horizontalHeaderItems.count()) + return false; + + beginRemoveColumns(QModelIndex(), column, column + count - 1); + QTableWidgetItem *oldItem = 0; + for (int row = rowCount() - 1; row >= 0; --row) { + int i = tableIndex(row, column); + for (int j = i; j < i + count; ++j) { + oldItem = tableItems.at(j); + if (oldItem) + oldItem->view = 0; + delete oldItem; + } + tableItems.remove(i, count); + } + for (int h=column; h<column+count; ++h) { + oldItem = horizontalHeaderItems.at(h); + if (oldItem) + oldItem->view = 0; + delete oldItem; + } + horizontalHeaderItems.remove(column, count); + endRemoveColumns(); + return true; +} + +void QTableModel::setItem(int row, int column, QTableWidgetItem *item) +{ + int i = tableIndex(row, column); + if (i < 0 || i >= tableItems.count()) + return; + QTableWidgetItem *oldItem = tableItems.at(i); + if (item == oldItem) + return; + + // remove old + if (oldItem) + oldItem->view = 0; + delete tableItems.at(i); + + QTableWidget *view = qobject_cast<QTableWidget*>(QObject::parent()); + + // set new + if (item) + item->d->id = i; + tableItems[i] = item; + + if (view && view->isSortingEnabled() + && view->horizontalHeader()->sortIndicatorSection() == column) { + // sorted insertion + Qt::SortOrder order = view->horizontalHeader()->sortIndicatorOrder(); + QVector<QTableWidgetItem*> colItems = columnItems(column); + if (row < colItems.count()) + colItems.remove(row); + int sortedRow; + if (item == 0) { + // move to after all non-0 (sortable) items + sortedRow = colItems.count(); + } else { + QVector<QTableWidgetItem*>::iterator it; + it = sortedInsertionIterator(colItems.begin(), colItems.end(), order, item); + sortedRow = qMax((int)(it - colItems.begin()), 0); + } + if (sortedRow != row) { + emit layoutAboutToBeChanged(); + // move the items @ row to sortedRow + int cc = columnCount(); + QVector<QTableWidgetItem*> rowItems(cc); + for (int j = 0; j < cc; ++j) + rowItems[j] = tableItems.at(tableIndex(row, j)); + tableItems.remove(tableIndex(row, 0), cc); + tableItems.insert(tableIndex(sortedRow, 0), cc, 0); + for (int j = 0; j < cc; ++j) + tableItems[tableIndex(sortedRow, j)] = rowItems.at(j); + QTableWidgetItem *header = verticalHeaderItems.at(row); + verticalHeaderItems.remove(row); + verticalHeaderItems.insert(sortedRow, header); + // update persistent indexes + QModelIndexList oldPersistentIndexes = persistentIndexList(); + QModelIndexList newPersistentIndexes = oldPersistentIndexes; + updateRowIndexes(newPersistentIndexes, row, sortedRow); + changePersistentIndexList(oldPersistentIndexes, + newPersistentIndexes); + + emit layoutChanged(); + return; + } + } + QModelIndex idx = QAbstractTableModel::index(row, column); + emit dataChanged(idx, idx); +} + +QTableWidgetItem *QTableModel::takeItem(int row, int column) +{ + long i = tableIndex(row, column); + QTableWidgetItem *itm = tableItems.value(i); + if (itm) { + itm->view = 0; + itm->d->id = -1; + tableItems[i] = 0; + } + return itm; +} + +QTableWidgetItem *QTableModel::item(int row, int column) const +{ + return tableItems.value(tableIndex(row, column)); +} + +QTableWidgetItem *QTableModel::item(const QModelIndex &index) const +{ + if (!isValid(index)) + return 0; + return tableItems.at(tableIndex(index.row(), index.column())); +} + +void QTableModel::removeItem(QTableWidgetItem *item) +{ + int i = tableItems.indexOf(item); + if (i != -1) { + tableItems[i] = 0; + QModelIndex idx = index(item); + emit dataChanged(idx, idx); + return; + } + + i = verticalHeaderItems.indexOf(item); + + if (i != -1) { + verticalHeaderItems[i] = 0; + emit headerDataChanged(Qt::Vertical, i, i); + return; + } + i = horizontalHeaderItems.indexOf(item); + if (i != -1) { + horizontalHeaderItems[i] = 0; + emit headerDataChanged(Qt::Horizontal, i, i); + return; + } +} + +void QTableModel::setHorizontalHeaderItem(int section, QTableWidgetItem *item) +{ + if (section < 0 || section >= horizontalHeaderItems.count()) + return; + QTableWidgetItem *oldItem = horizontalHeaderItems.at(section); + if (item == oldItem) + return; + + if (oldItem) + oldItem->view = 0; + delete oldItem; + + QTableWidget *view = qobject_cast<QTableWidget*>(QObject::parent()); + + if (item) { + item->view = view; + item->itemFlags = Qt::ItemFlags(int(item->itemFlags)|ItemIsHeaderItem); + } + horizontalHeaderItems[section] = item; + emit headerDataChanged(Qt::Horizontal, section, section); +} + +void QTableModel::setVerticalHeaderItem(int section, QTableWidgetItem *item) +{ + if (section < 0 || section >= verticalHeaderItems.count()) + return; + QTableWidgetItem *oldItem = verticalHeaderItems.at(section); + if (item == oldItem) + return; + + if (oldItem) + oldItem->view = 0; + delete oldItem; + + QTableWidget *view = qobject_cast<QTableWidget*>(QObject::parent()); + + if (item) { + item->view = view; + item->itemFlags = Qt::ItemFlags(int(item->itemFlags)|ItemIsHeaderItem); + } + verticalHeaderItems[section] = item; + emit headerDataChanged(Qt::Vertical, section, section); +} + +QTableWidgetItem *QTableModel::takeHorizontalHeaderItem(int section) +{ + if (section < 0 || section >= horizontalHeaderItems.count()) + return 0; + QTableWidgetItem *itm = horizontalHeaderItems.at(section); + if (itm) { + itm->view = 0; + itm->itemFlags &= ~ItemIsHeaderItem; + horizontalHeaderItems[section] = 0; + } + return itm; +} + +QTableWidgetItem *QTableModel::takeVerticalHeaderItem(int section) +{ + if (section < 0 || section >= verticalHeaderItems.count()) + return 0; + QTableWidgetItem *itm = verticalHeaderItems.at(section); + if (itm) { + itm->view = 0; + itm->itemFlags &= ~ItemIsHeaderItem; + verticalHeaderItems[section] = 0; + } + return itm; +} + +QTableWidgetItem *QTableModel::horizontalHeaderItem(int section) +{ + return horizontalHeaderItems.value(section); +} + +QTableWidgetItem *QTableModel::verticalHeaderItem(int section) +{ + return verticalHeaderItems.value(section); +} + +QModelIndex QTableModel::index(const QTableWidgetItem *item) const +{ + if (!item) + return QModelIndex(); + int i = -1; + const int id = item->d->id; + if (id >= 0 && id < tableItems.count() && tableItems.at(id) == item) { + i = id; + } else { // we need to search for the item + i = tableItems.indexOf(const_cast<QTableWidgetItem*>(item)); + if (i == -1) // not found + return QModelIndex(); + } + int row = i / columnCount(); + int col = i % columnCount(); + return QAbstractTableModel::index(row, col); +} + +void QTableModel::setRowCount(int rows) +{ + int rc = verticalHeaderItems.count(); + if (rows < 0 || rc == rows) + return; + if (rc < rows) + insertRows(qMax(rc, 0), rows - rc); + else + removeRows(qMax(rows, 0), rc - rows); +} + +void QTableModel::setColumnCount(int columns) +{ + int cc = horizontalHeaderItems.count(); + if (columns < 0 || cc == columns) + return; + if (cc < columns) + insertColumns(qMax(cc, 0), columns - cc); + else + removeColumns(qMax(columns, 0), cc - columns); +} + +int QTableModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : verticalHeaderItems.count(); +} + +int QTableModel::columnCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : horizontalHeaderItems.count(); +} + +QVariant QTableModel::data(const QModelIndex &index, int role) const +{ + QTableWidgetItem *itm = item(index); + if (itm) + return itm->data(role); + return QVariant(); +} + +bool QTableModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid()) + return false; + + QTableWidgetItem *itm = item(index); + if (itm) { + itm->setData(role, value); + return true; + } + + // don't create dummy table items for empty values + if (!value.isValid()) + return false; + + QTableWidget *view = qobject_cast<QTableWidget*>(QObject::parent()); + if (!view) + return false; + + itm = createItem(); + itm->setData(role, value); + view->setItem(index.row(), index.column(), itm); + return true; +} + +QMap<int, QVariant> QTableModel::itemData(const QModelIndex &index) const +{ + QMap<int, QVariant> roles; + QTableWidgetItem *itm = item(index); + if (itm) { + for (int i = 0; i < itm->values.count(); ++i) { + roles.insert(itm->values.at(i).role, + itm->values.at(i).value); + } + } + return roles; +} + +// reimplemented to ensure that only one dataChanged() signal is emitted +bool QTableModel::setItemData(const QModelIndex &index, const QMap<int, QVariant> &roles) +{ + if (!index.isValid()) + return false; + + QTableWidget *view = qobject_cast<QTableWidget*>(QObject::parent()); + QTableWidgetItem *itm = item(index); + if (itm) { + itm->view = 0; // prohibits item from calling itemChanged() + bool changed = false; + for (QMap<int, QVariant>::ConstIterator it = roles.constBegin(); it != roles.constEnd(); ++it) { + if (itm->data(it.key()) != it.value()) { + itm->setData(it.key(), it.value()); + changed = true; + } + } + itm->view = view; + if (changed) + itemChanged(itm); + return true; + } + + if (!view) + return false; + + itm = createItem(); + for (QMap<int, QVariant>::ConstIterator it = roles.constBegin(); it != roles.constEnd(); ++it) + itm->setData(it.key(), it.value()); + view->setItem(index.row(), index.column(), itm); + return true; +} + +Qt::ItemFlags QTableModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::ItemIsDropEnabled; + if (QTableWidgetItem *itm = item(index)) + return itm->flags(); + return (Qt::ItemIsEditable + |Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled); +} + +void QTableModel::sort(int column, Qt::SortOrder order) +{ + QVector<QPair<QTableWidgetItem*, int> > sortable; + QVector<int> unsortable; + + sortable.reserve(rowCount()); + unsortable.reserve(rowCount()); + + for (int row = 0; row < rowCount(); ++row) { + if (QTableWidgetItem *itm = item(row, column)) + sortable.append(QPair<QTableWidgetItem*,int>(itm, row)); + else + unsortable.append(row); + } + + LessThan compare = (order == Qt::AscendingOrder ? &itemLessThan : &itemGreaterThan); + qStableSort(sortable.begin(), sortable.end(), compare); + + QVector<QTableWidgetItem*> sorted_table(tableItems.count()); + QModelIndexList from; + QModelIndexList to; + for (int i = 0; i < rowCount(); ++i) { + int r = (i < sortable.count() + ? sortable.at(i).second + : unsortable.at(i - sortable.count())); + for (int c = 0; c < columnCount(); ++c) { + sorted_table[tableIndex(i, c)] = item(r, c); + from.append(createIndex(r, c, 0)); + to.append(createIndex(i, c, 0)); + } + } + + emit layoutAboutToBeChanged(); + + tableItems = sorted_table; + changePersistentIndexList(from, to); // ### slow + + emit layoutChanged(); +} + +bool QTableModel::canConvertToDouble(const QVariant &value) +{ + switch (value.type()) { + case QVariant::Bool: + case QVariant::Int: + case QVariant::UInt: + case QVariant::LongLong: + case QVariant::ULongLong: + case QVariant::Double: + case QVariant::Char: + return true; + default: + return false; + } + return false; +} + + +/* + \internal + + Ensures that rows in the interval [start, end] are + sorted according to the contents of column \a column + and the given sort \a order. +*/ +void QTableModel::ensureSorted(int column, Qt::SortOrder order, + int start, int end) +{ + int count = end - start + 1; + QVector < QPair<QTableWidgetItem*,int> > sorting; + sorting.reserve(count); + for (int row = start; row <= end; ++row) { + QTableWidgetItem *itm = item(row, column); + if (itm == 0) { + // no more sortable items (all 0-items are + // at the end of the table when it is sorted) + break; + } + sorting.append(QPair<QTableWidgetItem*,int>(itm, row)); + } + + LessThan compare = (order == Qt::AscendingOrder ? &itemLessThan : &itemGreaterThan); + qStableSort(sorting.begin(), sorting.end(), compare); + + QModelIndexList oldPersistentIndexes = persistentIndexList(); + QModelIndexList newPersistentIndexes = oldPersistentIndexes; + QVector<QTableWidgetItem*> newTable = tableItems; + QVector<QTableWidgetItem*> newVertical = verticalHeaderItems; + QVector<QTableWidgetItem*> colItems = columnItems(column); + QVector<QTableWidgetItem*>::iterator vit = colItems.begin(); + bool changed = false; + for (int i = 0; i < sorting.count(); ++i) { + int oldRow = sorting.at(i).second; + QTableWidgetItem *item = colItems.at(oldRow); + colItems.remove(oldRow); + vit = sortedInsertionIterator(vit, colItems.end(), order, item); + int newRow = qMax((int)(vit - colItems.begin()), 0); + vit = colItems.insert(vit, item); + if (newRow != oldRow) { + changed = true; + // move the items @ oldRow to newRow + int cc = columnCount(); + QVector<QTableWidgetItem*> rowItems(cc); + for (int j = 0; j < cc; ++j) + rowItems[j] = newTable.at(tableIndex(oldRow, j)); + newTable.remove(tableIndex(oldRow, 0), cc); + newTable.insert(tableIndex(newRow, 0), cc, 0); + for (int j = 0; j < cc; ++j) + newTable[tableIndex(newRow, j)] = rowItems.at(j); + QTableWidgetItem *header = newVertical.at(oldRow); + newVertical.remove(oldRow); + newVertical.insert(newRow, header); + // update persistent indexes + updateRowIndexes(newPersistentIndexes, oldRow, newRow); + // the index of the remaining rows may have changed + for (int j = i + 1; j < sorting.count(); ++j) { + int otherRow = sorting.at(j).second; + if (oldRow < otherRow && newRow >= otherRow) + --sorting[j].second; + else if (oldRow > otherRow && newRow <= otherRow) + ++sorting[j].second; + } + } + } + + if (changed) { + emit layoutAboutToBeChanged(); + tableItems = newTable; + verticalHeaderItems = newVertical; + changePersistentIndexList(oldPersistentIndexes, + newPersistentIndexes); + emit layoutChanged(); + } +} + +/* + \internal + + Returns the non-0 items in column \a column. +*/ +QVector<QTableWidgetItem*> QTableModel::columnItems(int column) const +{ + QVector<QTableWidgetItem*> items; + int rc = rowCount(); + items.reserve(rc); + for (int row = 0; row < rc; ++row) { + QTableWidgetItem *itm = item(row, column); + if (itm == 0) { + // no more sortable items (all 0-items are + // at the end of the table when it is sorted) + break; + } + items.append(itm); + } + return items; +} + +/* + \internal + + Adjusts the row of each index in \a indexes if necessary, given + that a row of items has been moved from row \a movedFrom to row + \a movedTo. +*/ +void QTableModel::updateRowIndexes(QModelIndexList &indexes, + int movedFromRow, int movedToRow) +{ + QModelIndexList::iterator it; + for (it = indexes.begin(); it != indexes.end(); ++it) { + int oldRow = (*it).row(); + int newRow = oldRow; + if (oldRow == movedFromRow) + newRow = movedToRow; + else if (movedFromRow < oldRow && movedToRow >= oldRow) + newRow = oldRow - 1; + else if (movedFromRow > oldRow && movedToRow <= oldRow) + newRow = oldRow + 1; + if (newRow != oldRow) + *it = index(newRow, (*it).column(), (*it).parent()); + } +} + +/* + \internal + + Returns an iterator to the item where \a item should be + inserted in the interval (\a begin, \a end) according to + the given sort \a order. +*/ +QVector<QTableWidgetItem*>::iterator QTableModel::sortedInsertionIterator( + const QVector<QTableWidgetItem*>::iterator &begin, + const QVector<QTableWidgetItem*>::iterator &end, + Qt::SortOrder order, QTableWidgetItem *item) +{ + if (order == Qt::AscendingOrder) + return qLowerBound(begin, end, item, QTableModelLessThan()); + return qLowerBound(begin, end, item, QTableModelGreaterThan()); +} + +bool QTableModel::itemLessThan(const QPair<QTableWidgetItem*,int> &left, + const QPair<QTableWidgetItem*,int> &right) +{ + return *(left.first) < *(right.first); +} + +bool QTableModel::itemGreaterThan(const QPair<QTableWidgetItem*,int> &left, + const QPair<QTableWidgetItem*,int> &right) +{ + return (*(right.first) < *(left .first)); +} + +QVariant QTableModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (section < 0) + return QVariant(); + + QTableWidgetItem *itm = 0; + if (orientation == Qt::Horizontal && section < horizontalHeaderItems.count()) + itm = horizontalHeaderItems.at(section); + else if (orientation == Qt::Vertical && section < verticalHeaderItems.count()) + itm = verticalHeaderItems.at(section); + else + return QVariant(); // section is out of bounds + + if (itm) + return itm->data(role); + if (role == Qt::DisplayRole) + return section + 1; + return QVariant(); +} + +bool QTableModel::setHeaderData(int section, Qt::Orientation orientation, + const QVariant &value, int role) +{ + if (section < 0 || + (orientation == Qt::Horizontal && horizontalHeaderItems.size() <= section) || + (orientation == Qt::Vertical && verticalHeaderItems.size() <= section)) + return false; + + QTableWidgetItem *itm = 0; + if (orientation == Qt::Horizontal) + itm = horizontalHeaderItems.at(section); + else + itm = verticalHeaderItems.at(section); + if (itm) { + itm->setData(role, value); + return true; + } + return false; +} + +bool QTableModel::isValid(const QModelIndex &index) const +{ + return (index.isValid() + && index.row() < verticalHeaderItems.count() + && index.column() < horizontalHeaderItems.count()); +} + +void QTableModel::clear() +{ + for (int j = 0; j < verticalHeaderItems.count(); ++j) { + if (verticalHeaderItems.at(j)) { + verticalHeaderItems.at(j)->view = 0; + delete verticalHeaderItems.at(j); + verticalHeaderItems[j] = 0; + } + } + for (int k = 0; k < horizontalHeaderItems.count(); ++k) { + if (horizontalHeaderItems.at(k)) { + horizontalHeaderItems.at(k)->view = 0; + delete horizontalHeaderItems.at(k); + horizontalHeaderItems[k] = 0; + } + } + clearContents(); +} + +void QTableModel::clearContents() +{ + for (int i = 0; i < tableItems.count(); ++i) { + if (tableItems.at(i)) { + tableItems.at(i)->view = 0; + delete tableItems.at(i); + tableItems[i] = 0; + } + } + reset(); +} + +void QTableModel::itemChanged(QTableWidgetItem *item) +{ + if (!item) + return; + if (item->flags() & ItemIsHeaderItem) { + int row = verticalHeaderItems.indexOf(item); + if (row >= 0) { + emit headerDataChanged(Qt::Vertical, row, row); + } else { + int column = horizontalHeaderItems.indexOf(item); + if (column >= 0) + emit headerDataChanged(Qt::Horizontal, column, column); + } + } else { + QModelIndex idx = index(item); + if (idx.isValid()) + emit dataChanged(idx, idx); + } +} + +QTableWidgetItem* QTableModel::createItem() const +{ + return prototype ? prototype->clone() : new QTableWidgetItem; +} + +const QTableWidgetItem *QTableModel::itemPrototype() const +{ + return prototype; +} + +void QTableModel::setItemPrototype(const QTableWidgetItem *item) +{ + if (prototype != item) { + delete prototype; + prototype = item; + } +} + +QStringList QTableModel::mimeTypes() const +{ + const QTableWidget *view = qobject_cast<const QTableWidget*>(QObject::parent()); + return (view ? view->mimeTypes() : QStringList()); +} + +QMimeData *QTableModel::internalMimeData() const +{ + return QAbstractTableModel::mimeData(cachedIndexes); +} + +QMimeData *QTableModel::mimeData(const QModelIndexList &indexes) const +{ + QList<QTableWidgetItem*> items; + for (int i = 0; i < indexes.count(); ++i) + items << item(indexes.at(i)); + const QTableWidget *view = qobject_cast<const QTableWidget*>(QObject::parent()); + + // cachedIndexes is a little hack to avoid copying from QModelIndexList to + // QList<QTreeWidgetItem*> and back again in the view + cachedIndexes = indexes; + QMimeData *mimeData = (view ? view->mimeData(items) : 0); + cachedIndexes.clear(); + return mimeData; +} + +bool QTableModel::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row , int column, const QModelIndex &index) +{ + if (index.isValid()) { + row = index.row(); + column = index.column(); + }else if (row == -1 || column == -1) { // The user dropped outside the table. + row = rowCount(); + column = 0; + } + + QTableWidget *view = qobject_cast<QTableWidget*>(QObject::parent()); + return (view ? view->dropMimeData(row, column, data, action) : false); +} + +Qt::DropActions QTableModel::supportedDropActions() const +{ + const QTableWidget *view = qobject_cast<const QTableWidget*>(QObject::parent()); + return (view ? view->supportedDropActions() : Qt::DropActions(Qt::IgnoreAction)); +} + +/*! + \class QTableWidgetSelectionRange + + \brief The QTableWidgetSelectionRange class provides a way to interact with + selection in a model without using model indexes and a selection model. + + \ingroup model-view + + The QTableWidgetSelectionRange class stores the top left and bottom + right rows and columns of a selection range in a table. The + selections in the table may consist of several selection ranges. + + \note If the item within the selection range is marked as not selectable, + e.g., \c{itemFlags() & Qt::ItemIsSelectable == 0} then it will not appear + in the selection range. + + \sa QTableWidget +*/ + +/*! + Constructs an table selection range, i.e. a range + whose rowCount() and columnCount() are 0. +*/ +QTableWidgetSelectionRange::QTableWidgetSelectionRange() + : top(-1), left(-1), bottom(-2), right(-2) +{ +} + +/*! + Constructs the table selection range from the given \a top, \a + left, \a bottom and \a right table rows and columns. + + \sa topRow(), leftColumn(), bottomRow(), rightColumn() +*/ +QTableWidgetSelectionRange::QTableWidgetSelectionRange(int top, int left, int bottom, int right) + : top(top), left(left), bottom(bottom), right(right) +{ +} + +/*! + Constructs a the table selection range by copying the given \a + other table selection range. +*/ +QTableWidgetSelectionRange::QTableWidgetSelectionRange(const QTableWidgetSelectionRange &other) + : top(other.top), left(other.left), bottom(other.bottom), right(other.right) +{ +} + +/*! + Destroys the table selection range. +*/ +QTableWidgetSelectionRange::~QTableWidgetSelectionRange() +{ +} + +/*! + \fn int QTableWidgetSelectionRange::topRow() const + + Returns the top row of the range. + + \sa bottomRow(), leftColumn(), rowCount() +*/ + +/*! + \fn int QTableWidgetSelectionRange::bottomRow() const + + Returns the bottom row of the range. + + \sa topRow(), rightColumn(), rowCount() +*/ + +/*! + \fn int QTableWidgetSelectionRange::leftColumn() const + + Returns the left column of the range. + + \sa rightColumn(), topRow(), columnCount() +*/ + +/*! + \fn int QTableWidgetSelectionRange::rightColumn() const + + Returns the right column of the range. + + \sa leftColumn(), bottomRow(), columnCount() +*/ + +/*! + \since 4.1 + \fn int QTableWidgetSelectionRange::rowCount() const + + Returns the number of rows in the range. + + This is equivalent to bottomRow() - topRow() + 1. + + \sa columnCount(), topRow(), bottomRow() +*/ + +/*! + \since 4.1 + \fn int QTableWidgetSelectionRange::columnCount() const + + Returns the number of columns in the range. + + This is equivalent to rightColumn() - leftColumn() + 1. + + \sa rowCount(), leftColumn(), rightColumn() +*/ + +/*! + \class QTableWidgetItem + \brief The QTableWidgetItem class provides an item for use with the + QTableWidget class. + + \ingroup model-view + + Table items are used to hold pieces of information for table widgets. + Items usually contain text, icons, or checkboxes + + The QTableWidgetItem class is a convenience class that replaces the + \c QTableItem class in Qt 3. It provides an item for use with + the QTableWidget class. + + Top-level items are constructed without a parent then inserted at the + position specified by a pair of row and column numbers: + + \snippet doc/src/snippets/qtablewidget-using/mainwindow.cpp 3 + + Each item can have its own background brush which is set with + the setBackground() function. The current background brush can be + found with background(). + The text label for each item can be rendered with its own font and brush. + These are specified with the setFont() and setForeground() functions, + and read with font() and foreground(). + + By default, items are enabled, editable, selectable, checkable, and can be + used both as the source of a drag and drop operation and as a drop target. + Each item's flags can be changed by calling setFlags() with the appropriate + value (see \l{Qt::ItemFlags}). Checkable items can be checked and unchecked + with the setCheckState() function. The corresponding checkState() function + indicates whether the item is currently checked. + + \section1 Subclassing + + When subclassing QTableWidgetItem to provide custom items, it is possible to + define new types for them so that they can be distinguished from standard + items. The constructors for subclasses that require this feature need to + call the base class constructor with a new type value equal to or greater + than \l UserType. + + \sa QTableWidget, {Model/View Programming}, QListWidgetItem, QTreeWidgetItem +*/ + +/*! + \fn int QTableWidgetItem::row() const + \since 4.2 + + Returns the row of the item in the table. + If the item is not in a table, this function will return -1. + + \sa column() +*/ + +/*! + \fn int QTableWidgetItem::column() const + \since 4.2 + + Returns the column of the item in the table. + If the item is not in a table, this function will return -1. + + \sa row() +*/ + +/*! + \fn void QTableWidgetItem::setSelected(bool select) + \since 4.2 + + Sets the selected state of the item to \a select. + + \sa isSelected() +*/ + +/*! + \fn bool QTableWidgetItem::isSelected() const + \since 4.2 + + Returns true if the item is selected, otherwise returns false. + + \sa setSelected() +*/ + +/*! + \fn QSize QTableWidgetItem::sizeHint() const + \since 4.1 + + Returns the size hint set for the table item. +*/ + +/*! + \fn void QTableWidgetItem::setSizeHint(const QSize &size) + \since 4.1 + + Sets the size hint for the table item to be \a size. + If no size hint is set, the item delegate will compute the + size hint based on the item data. +*/ + +/*! + \fn Qt::CheckState QTableWidgetItem::checkState() const + + Returns the checked state of the table item. + + \sa flags() +*/ + +/*! + \fn void QTableWidgetItem::setCheckState(Qt::CheckState state) + + Sets the check state of the table item to be \a state. +*/ + +/*! + \fn QTableWidget *QTableWidgetItem::tableWidget() const + + Returns the table widget that contains the item. +*/ + +/*! + \fn Qt::ItemFlags QTableWidgetItem::flags() const + + Returns the flags used to describe the item. These determine whether + the item can be checked, edited, and selected. + + \sa setFlags() +*/ + +/*! + \fn void QTableWidgetItem::setFlags(Qt::ItemFlags flags) + + Sets the flags for the item to the given \a flags. These determine whether + the item can be selected or modified. + + \sa flags() +*/ +void QTableWidgetItem::setFlags(Qt::ItemFlags aflags) +{ + itemFlags = aflags; + if (QTableModel *model = (view ? qobject_cast<QTableModel*>(view->model()) : 0)) + model->itemChanged(this); +} + + +/*! + \fn QString QTableWidgetItem::text() const + + Returns the item's text. + + \sa setText() +*/ + +/*! + \fn void QTableWidgetItem::setText(const QString &text) + + Sets the item's text to the \a text specified. + + \sa text() setFont() setForeground() +*/ + +/*! + \fn QIcon QTableWidgetItem::icon() const + + Returns the item's icon. + + \sa setIcon(), {QAbstractItemView::iconSize}{iconSize} +*/ + +/*! + \fn void QTableWidgetItem::setIcon(const QIcon &icon) + + Sets the item's icon to the \a icon specified. + + \sa icon(), setText(), {QAbstractItemView::iconSize}{iconSize} +*/ + +/*! + \fn QString QTableWidgetItem::statusTip() const + + Returns the item's status tip. + + \sa setStatusTip() +*/ + +/*! + \fn void QTableWidgetItem::setStatusTip(const QString &statusTip) + + Sets the status tip for the table item to the text specified by + \a statusTip. QTableWidget mouse tracking needs to be enabled for this + feature to work. + + \sa statusTip() setToolTip() setWhatsThis() +*/ + +/*! + \fn QString QTableWidgetItem::toolTip() const + + Returns the item's tooltip. + + \sa setToolTip() +*/ + +/*! + \fn void QTableWidgetItem::setToolTip(const QString &toolTip) + + Sets the item's tooltip to the string specified by \a toolTip. + + \sa toolTip() setStatusTip() setWhatsThis() +*/ + +/*! + \fn QString QTableWidgetItem::whatsThis() const + + Returns the item's "What's This?" help. + + \sa setWhatsThis() +*/ + +/*! + \fn void QTableWidgetItem::setWhatsThis(const QString &whatsThis) + + Sets the item's "What's This?" help to the string specified by \a whatsThis. + + \sa whatsThis() setStatusTip() setToolTip() +*/ + +/*! + \fn QFont QTableWidgetItem::font() const + + Returns the font used to render the item's text. + + \sa setFont() +*/ + +/*! + \fn void QTableWidgetItem::setFont(const QFont &font) + + Sets the font used to display the item's text to the given \a font. + + \sa font() setText() setForeground() +*/ + +/*! + \fn QColor QTableWidgetItem::backgroundColor() const + \obsolete + + This function is deprecated. Use background() instead. +*/ + +/*! + \fn void QTableWidgetItem::setBackgroundColor(const QColor &color) + \obsolete + + This function is deprecated. Use setBackground() instead. +*/ + +/*! + \fn QBrush QTableWidgetItem::background() const + \since 4.2 + + Returns the brush used to render the item's background. + + \sa foreground() +*/ + +/*! + \fn void QTableWidgetItem::setBackground(const QBrush &brush) + \since 4.2 + + Sets the item's background brush to the specified \a brush. + + \sa setForeground() +*/ + +/*! + \fn QColor QTableWidgetItem::textColor() const + \obsolete + + This function is deprecated. Use foreground() instead. +*/ + +/*! + \fn void QTableWidgetItem::setTextColor(const QColor &color) + \obsolete + + This function is deprecated. Use setForeground() instead. +*/ + +/*! + \fn QBrush QTableWidgetItem::foreground() const + \since 4.2 + + Returns the brush used to render the item's foreground (e.g. text). + + \sa background() +*/ + +/*! + \fn void QTableWidgetItem::setForeground(const QBrush &brush) + \since 4.2 + + Sets the item's foreground brush to the specified \a brush. + + \sa setBackground() +*/ + +/*! + \fn int QTableWidgetItem::textAlignment() const + + Returns the text alignment for the item's text. + + \sa Qt::Alignment +*/ + +/*! + \fn void QTableWidgetItem::setTextAlignment(int alignment) + + Sets the text alignment for the item's text to the \a alignment + specified. + + \sa Qt::Alignment +*/ + +/*! + Constructs a table item of the specified \a type that does not belong + to any table. + + \sa type() +*/ +QTableWidgetItem::QTableWidgetItem(int type) + : rtti(type), view(0), d(new QTableWidgetItemPrivate(this)), + itemFlags(Qt::ItemIsEditable + |Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled) +{ +} + +/*! + Constructs a table item with the given \a text. + + \sa type() +*/ +QTableWidgetItem::QTableWidgetItem(const QString &text, int type) + : rtti(type), view(0), d(new QTableWidgetItemPrivate(this)), + itemFlags(Qt::ItemIsEditable + |Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled) +{ + setData(Qt::DisplayRole, text); +} + +/*! + Constructs a table item with the given \a icon and \a text. + + \sa type() +*/ +QTableWidgetItem::QTableWidgetItem(const QIcon &icon, const QString &text, int type) + : rtti(type), view(0), d(new QTableWidgetItemPrivate(this)), + itemFlags(Qt::ItemIsEditable + |Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled) +{ + setData(Qt::DecorationRole, icon); + setData(Qt::DisplayRole, text); +} + +/*! + Destroys the table item. +*/ +QTableWidgetItem::~QTableWidgetItem() +{ + if (QTableModel *model = (view ? qobject_cast<QTableModel*>(view->model()) : 0)) + model->removeItem(this); + view = 0; + delete d; +} + +/*! + Creates a copy of the item. +*/ +QTableWidgetItem *QTableWidgetItem::clone() const +{ + return new QTableWidgetItem(*this); +} + +/*! + Sets the item's data for the given \a role to the specified \a value. + + \sa Qt::ItemDataRole, data() +*/ +void QTableWidgetItem::setData(int role, const QVariant &value) +{ + bool found = false; + role = (role == Qt::EditRole ? Qt::DisplayRole : role); + for (int i = 0; i < values.count(); ++i) { + if (values.at(i).role == role) { + if (values[i].value == value) + return; + + values[i].value = value; + found = true; + break; + } + } + if (!found) + values.append(QWidgetItemData(role, value)); + if (QTableModel *model = (view ? qobject_cast<QTableModel*>(view->model()) : 0)) + model->itemChanged(this); +} + +/*! + Returns the item's data for the given \a role. +*/ +QVariant QTableWidgetItem::data(int role) const +{ + role = (role == Qt::EditRole ? Qt::DisplayRole : role); + for (int i = 0; i < values.count(); ++i) + if (values.at(i).role == role) + return values.at(i).value; + return QVariant(); +} + +/*! + Returns true if the item is less than the \a other item; otherwise returns + false. +*/ +bool QTableWidgetItem::operator<(const QTableWidgetItem &other) const +{ + const QVariant v1 = data(Qt::DisplayRole), v2 = other.data(Qt::DisplayRole); + if (QTableModel::canConvertToDouble(v1) && QTableModel::canConvertToDouble(v2)) + return v1.toDouble() < v2.toDouble(); + return v1.toString() < v2.toString(); +} + +#ifndef QT_NO_DATASTREAM + +/*! + Reads the item from stream \a in. + + \sa write() +*/ +void QTableWidgetItem::read(QDataStream &in) +{ + in >> values; +} + +/*! + Writes the item to stream \a out. + + \sa read() +*/ +void QTableWidgetItem::write(QDataStream &out) const +{ + out << values; +} + +/*! + \relates QTableWidgetItem + + Reads a table widget item from stream \a in into \a item. + + This operator uses QTableWidgetItem::read(). + + \sa {Format of the QDataStream Operators} +*/ +QDataStream &operator>>(QDataStream &in, QTableWidgetItem &item) +{ + item.read(in); + return in; +} + +/*! + \relates QTableWidgetItem + + Writes the table widget item \a item to stream \a out. + + This operator uses QTableWidgetItem::write(). + + \sa {Format of the QDataStream Operators} +*/ +QDataStream &operator<<(QDataStream &out, const QTableWidgetItem &item) +{ + item.write(out); + return out; +} + +#endif // QT_NO_DATASTREAM + +/*! + \since 4.1 + + Constructs a copy of \a other. Note that type() and tableWidget() + are not copied. + + This function is useful when reimplementing clone(). + + \sa data(), flags() +*/ +QTableWidgetItem::QTableWidgetItem(const QTableWidgetItem &other) + : rtti(Type), values(other.values), view(0), + d(new QTableWidgetItemPrivate(this)), + itemFlags(other.itemFlags) +{ +} + +/*! + Assigns \a other's data and flags to this item. Note that type() + and tableWidget() are not copied. + + This function is useful when reimplementing clone(). + + \sa data(), flags() +*/ +QTableWidgetItem &QTableWidgetItem::operator=(const QTableWidgetItem &other) +{ + values = other.values; + itemFlags = other.itemFlags; + return *this; +} + +/*! + \class QTableWidget + \brief The QTableWidget class provides an item-based table view with a default model. + + \ingroup model-view + \mainclass + + Table widgets provide standard table display facilities for applications. + The items in a QTableWidget are provided by QTableWidgetItem. + + If you want a table that uses your own data model you should + use QTableView rather than this class. + + Table widgets can be constructed with the required numbers of rows and + columns: + + \snippet doc/src/snippets/qtablewidget-using/mainwindow.cpp 0 + + Alternatively, tables can be constructed without a given size and resized + later: + + \snippet doc/src/snippets/qtablewidget-resizing/mainwindow.cpp 0 + \snippet doc/src/snippets/qtablewidget-resizing/mainwindow.cpp 1 + + Items are created ouside the table (with no parent widget) and inserted + into the table with setItem(): + + \snippet doc/src/snippets/qtablewidget-resizing/mainwindow.cpp 2 + + If you want to enable sorting in your table widget, do so after you + have populated it with items, otherwise sorting may interfere with + the insertion order (see setItem() for details). + + Tables can be given both horizontal and vertical headers. The simplest way + to create the headers is to supply a list of strings to the + setHorizontalHeaderLabels() and setVerticalHeaderLabels() functions. These + will provide simple textual headers for the table's columns and rows. + More sophisticated headers can be created from existing table items + that are usually constructed outside the table. For example, we can + construct a table item with an icon and aligned text, and use it as the + header for a particular column: + + \snippet doc/src/snippets/qtablewidget-using/mainwindow.cpp 2 + + The number of rows in the table can be found with rowCount(), and the + number of columns with columnCount(). The table can be cleared with the + clear() function. + + \table 100% + \row \o \inlineimage windowsxp-tableview.png Screenshot of a Windows XP style table widget + \o \inlineimage macintosh-tableview.png Screenshot of a Macintosh style table widget + \o \inlineimage plastique-tableview.png Screenshot of a Plastique style table widget + \row \o A \l{Windows XP Style Widget Gallery}{Windows XP style} table widget. + \o A \l{Macintosh Style Widget Gallery}{Macintosh style} table widget. + \o A \l{Plastique Style Widget Gallery}{Plastique style} table widget. + \endtable + + \sa QTableWidgetItem, QTableView, {Model/View Programming} +*/ + +/*! + \property QTableWidget::rowCount + \brief the number of rows in the table + + By default, for a table constructed without row and column counts, + this property contains a value of 0. +*/ + +/*! + \property QTableWidget::columnCount + \brief the number of columns in the table + + By default, for a table constructed without row and column counts, + this property contains a value of 0. +*/ + +void QTableWidgetPrivate::setup() +{ + Q_Q(QTableWidget); + // view signals + QObject::connect(q, SIGNAL(pressed(QModelIndex)), q, SLOT(_q_emitItemPressed(QModelIndex))); + QObject::connect(q, SIGNAL(clicked(QModelIndex)), q, SLOT(_q_emitItemClicked(QModelIndex))); + QObject::connect(q, SIGNAL(doubleClicked(QModelIndex)), + q, SLOT(_q_emitItemDoubleClicked(QModelIndex))); + QObject::connect(q, SIGNAL(activated(QModelIndex)), q, SLOT(_q_emitItemActivated(QModelIndex))); + QObject::connect(q, SIGNAL(entered(QModelIndex)), q, SLOT(_q_emitItemEntered(QModelIndex))); + // model signals + QObject::connect(model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), + q, SLOT(_q_emitItemChanged(QModelIndex))); + // selection signals + QObject::connect(q->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + q, SLOT(_q_emitCurrentItemChanged(QModelIndex,QModelIndex))); + QObject::connect(q->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + q, SIGNAL(itemSelectionChanged())); + // sorting + QObject::connect(model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), + q, SLOT(_q_dataChanged(QModelIndex,QModelIndex))); + QObject::connect(model(), SIGNAL(columnsRemoved(QModelIndex,int,int)), q, SLOT(_q_sort())); +} + +void QTableWidgetPrivate::_q_emitItemPressed(const QModelIndex &index) +{ + Q_Q(QTableWidget); + if (QTableWidgetItem *item = model()->item(index)) + emit q->itemPressed(item); + emit q->cellPressed(index.row(), index.column()); +} + +void QTableWidgetPrivate::_q_emitItemClicked(const QModelIndex &index) +{ + Q_Q(QTableWidget); + if (QTableWidgetItem *item = model()->item(index)) + emit q->itemClicked(item); + emit q->cellClicked(index.row(), index.column()); +} + +void QTableWidgetPrivate::_q_emitItemDoubleClicked(const QModelIndex &index) +{ + Q_Q(QTableWidget); + if (QTableWidgetItem *item = model()->item(index)) + emit q->itemDoubleClicked(item); + emit q->cellDoubleClicked(index.row(), index.column()); +} + +void QTableWidgetPrivate::_q_emitItemActivated(const QModelIndex &index) +{ + Q_Q(QTableWidget); + if (QTableWidgetItem *item = model()->item(index)) + emit q->itemActivated(item); + emit q->cellActivated(index.row(), index.column()); +} + +void QTableWidgetPrivate::_q_emitItemEntered(const QModelIndex &index) +{ + Q_Q(QTableWidget); + if (QTableWidgetItem *item = model()->item(index)) + emit q->itemEntered(item); + emit q->cellEntered(index.row(), index.column()); +} + +void QTableWidgetPrivate::_q_emitItemChanged(const QModelIndex &index) +{ + Q_Q(QTableWidget); + if (QTableWidgetItem *item = model()->item(index)) + emit q->itemChanged(item); + emit q->cellChanged(index.row(), index.column()); +} + +void QTableWidgetPrivate::_q_emitCurrentItemChanged(const QModelIndex ¤t, + const QModelIndex &previous) +{ + Q_Q(QTableWidget); + QTableWidgetItem *currentItem = model()->item(current); + QTableWidgetItem *previousItem = model()->item(previous); + if (currentItem || previousItem) + emit q->currentItemChanged(currentItem, previousItem); + emit q->currentCellChanged(current.row(), current.column(), previous.row(), previous.column()); +} + +void QTableWidgetPrivate::_q_sort() +{ + Q_Q(QTableWidget); + if (sortingEnabled) { + int column = q->horizontalHeader()->sortIndicatorSection(); + Qt::SortOrder order = q->horizontalHeader()->sortIndicatorOrder(); + model()->sort(column, order); + } +} + +void QTableWidgetPrivate::_q_dataChanged(const QModelIndex &topLeft, + const QModelIndex &bottomRight) +{ + Q_Q(QTableWidget); + if (sortingEnabled && topLeft.isValid() && bottomRight.isValid()) { + int column = q->horizontalHeader()->sortIndicatorSection(); + if (column >= topLeft.column() && column <= bottomRight.column()) { + Qt::SortOrder order = q->horizontalHeader()->sortIndicatorOrder(); + model()->ensureSorted(column, order, topLeft.row(), bottomRight.row()); + } + } +} + +/*! + \fn void QTableWidget::itemPressed(QTableWidgetItem *item) + + This signal is emitted whenever an item in the table is pressed. + The \a item specified is the item that was pressed. +*/ + +/*! + \fn void QTableWidget::itemClicked(QTableWidgetItem *item) + + This signal is emitted whenever an item in the table is clicked. + The \a item specified is the item that was clicked. +*/ + +/*! + \fn void QTableWidget::itemDoubleClicked(QTableWidgetItem *item) + + This signal is emitted whenever an item in the table is double + clicked. The \a item specified is the item that was double clicked. +*/ + +/*! + \fn void QTableWidget::itemActivated(QTableWidgetItem *item) + + This signal is emitted when the specified \a item has been activated +*/ + +/*! + \fn void QTableWidget::itemEntered(QTableWidgetItem *item) + + This signal is emitted when the mouse cursor enters an item. The + \a item is the item entered. + + This signal is only emitted when mouseTracking is turned on, or when a + mouse button is pressed while moving into an item. +*/ + +/*! + \fn void QTableWidget::itemChanged(QTableWidgetItem *item) + + This signal is emitted whenever the data of \a item has changed. +*/ + +/*! + \fn void QTableWidget::currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous) + + This signal is emitted whenever the current item changes. The \a + previous item is the item that previously had the focus, \a + current is the new current item. +*/ + +/*! + \fn void QTableWidget::itemSelectionChanged() + + This signal is emitted whenever the selection changes. + + \sa selectedItems() isItemSelected() +*/ + + +/*! + \since 4.1 + \fn void QTableWidget::cellPressed(int row, int column) + + This signal is emitted whenever a cell in the table is pressed. + The \a row and \a column specified is the cell that was pressed. +*/ + +/*! + \since 4.1 + \fn void QTableWidget::cellClicked(int row, int column) + + This signal is emitted whenever a cell in the table is clicked. + The \a row and \a column specified is the cell that was clicked. +*/ + +/*! + \since 4.1 + \fn void QTableWidget::cellDoubleClicked(int row, int column) + + This signal is emitted whenever a cell in the table is double + clicked. The \a row and \a column specified is the cell that was + double clicked. +*/ + +/*! + \since 4.1 + \fn void QTableWidget::cellActivated(int row, int column) + + This signal is emitted when the cell specified by \a row and \a column + has been activated +*/ + +/*! + \since 4.1 + \fn void QTableWidget::cellEntered(int row, int column) + + This signal is emitted when the mouse cursor enters a cell. The + cell is specified by \a row and \a column. + + This signal is only emitted when mouseTracking is turned on, or when a + mouse button is pressed while moving into an item. +*/ + +/*! + \since 4.1 + \fn void QTableWidget::cellChanged(int row, int column) + + This signal is emitted whenever the data of the item in the cell + specified by \a row and \a column has changed. +*/ + +/*! + \since 4.1 + \fn void QTableWidget::currentCellChanged(int currentRow, int currentColumn, int previousRow, int previousColumn) + + This signal is emitted whenever the current cell changes. The cell + specified by \a previousRow and \a previousColumn is the cell that + previously had the focus, the cell specified by \a currentRow and \a + currentColumn is the new current cell. +*/ + +/*! + \since 4.3 + \fn void QTableWidget::removeCellWidget(int row, int column) + + Removes the widget set on the cell indicated by \a row and \a column. +*/ + +/*! + \fn QTableWidgetItem *QTableWidget::itemAt(int ax, int ay) const + + Returns the item at the position equivalent to QPoint(\a{ax}, \a{ay}) in + the table widget's coordinate system, or returns 0 if the specified point + is not covered by an item in the table widget. + + \sa item() +*/ + +/*! + \enum QTableWidgetItem::ItemType + + This enum describes the types that are used to describe table widget items. + + \value Type The default type for table widget items. + \value UserType The minimum value for custom types. Values below UserType are + reserved by Qt. + + You can define new user types in QTableWidgetItem subclasses to ensure that + custom items are treated specially. + + \sa type() +*/ + +/*! + \fn int QTableWidgetItem::type() const + + Returns the type passed to the QTableWidgetItem constructor. +*/ + +/*! + Creates a new table view with the given \a parent. +*/ +QTableWidget::QTableWidget(QWidget *parent) + : QTableView(*new QTableWidgetPrivate, parent) +{ + Q_D(QTableWidget); + QTableView::setModel(new QTableModel(0, 0, this)); + d->setup(); +} + +/*! + Creates a new table view with the given \a rows and \a columns, and with the given \a parent. +*/ +QTableWidget::QTableWidget(int rows, int columns, QWidget *parent) + : QTableView(*new QTableWidgetPrivate, parent) +{ + Q_D(QTableWidget); + QTableView::setModel(new QTableModel(rows, columns, this)); + d->setup(); +} + +/*! + Destroys this QTableWidget. +*/ +QTableWidget::~QTableWidget() +{ +} + +/*! + Sets the number of rows in this table's model to \a rows. If + this is less than rowCount(), the data in the unwanted rows + is discarded. + + \sa setColumnCount() +*/ +void QTableWidget::setRowCount(int rows) +{ + Q_D(QTableWidget); + d->model()->setRowCount(rows); +} + +/*! + Returns the number of rows. +*/ + +int QTableWidget::rowCount() const +{ + Q_D(const QTableWidget); + return d->model()->rowCount(); +} + +/*! + Sets the number of columns in this table's model to \a columns. If + this is less than columnCount(), the data in the unwanted columns + is discarded. + + \sa setRowCount() +*/ +void QTableWidget::setColumnCount(int columns) +{ + Q_D(QTableWidget); + d->model()->setColumnCount(columns); +} + +/*! + Returns the number of columns. +*/ + +int QTableWidget::columnCount() const +{ + Q_D(const QTableWidget); + return d->model()->columnCount(); +} + +/*! + Returns the row for the \a item. +*/ +int QTableWidget::row(const QTableWidgetItem *item) const +{ + Q_D(const QTableWidget); + return d->model()->index(item).row(); +} + +/*! + Returns the column for the \a item. +*/ +int QTableWidget::column(const QTableWidgetItem *item) const +{ + Q_D(const QTableWidget); + return d->model()->index(item).column(); +} + + +/*! + Returns the item for the given \a row and \a column if one has been set; otherwise + returns 0. + + \sa setItem() +*/ +QTableWidgetItem *QTableWidget::item(int row, int column) const +{ + Q_D(const QTableWidget); + return d->model()->item(row, column); +} + +/*! + Sets the item for the given \a row and \a column to \a item. + + The table takes ownership of the item. + + Note that if sorting is enabled (see + \l{QTableView::sortingEnabled} {sortingEnabled}) and \a column is + the current sort column, the \a row will be moved to the sorted + position determined by \a item. + + If you want to set several items of a particular row (say, by + calling setItem() in a loop), you may want to turn off sorting + before doing so, and turn it back on afterwards; this will allow + you to use the same \a row argument for all items in the same row + (i.e. setItem() will not move the row). + + \sa item() takeItem() +*/ +void QTableWidget::setItem(int row, int column, QTableWidgetItem *item) +{ + Q_D(QTableWidget); + if (item) { + if (item->view != 0) { + qWarning("QTableWidget: cannot insert an item that is already owned by another QTableWidget"); + } else { + item->view = this; + d->model()->setItem(row, column, item); + } + } else { + delete takeItem(row, column); + } +} + +/*! + Removes the item at \a row and \a column from the table without deleting it. +*/ +QTableWidgetItem *QTableWidget::takeItem(int row, int column) +{ + Q_D(QTableWidget); + QTableWidgetItem *item = d->model()->takeItem(row, column); + if (item) + item->view = 0; + return item; +} + +/*! + Returns the vertical header item for row \a row. +*/ +QTableWidgetItem *QTableWidget::verticalHeaderItem(int row) const +{ + Q_D(const QTableWidget); + return d->model()->verticalHeaderItem(row); +} + +/*! + Sets the vertical header item for row \a row to \a item. +*/ +void QTableWidget::setVerticalHeaderItem(int row, QTableWidgetItem *item) +{ + Q_D(QTableWidget); + if (item) { + item->view = this; + d->model()->setVerticalHeaderItem(row, item); + } else { + delete takeVerticalHeaderItem(row); + } +} + +/*! + \since 4.1 + Removes the vertical header item at \a row from the header without deleting it. +*/ +QTableWidgetItem *QTableWidget::takeVerticalHeaderItem(int row) +{ + Q_D(QTableWidget); + QTableWidgetItem *itm = d->model()->takeVerticalHeaderItem(row); + if (itm) + itm->view = 0; + return itm; +} + +/*! + Returns the horizontal header item for column, \a column, if one has been + set; otherwise returns 0. +*/ +QTableWidgetItem *QTableWidget::horizontalHeaderItem(int column) const +{ + Q_D(const QTableWidget); + return d->model()->horizontalHeaderItem(column); +} + +/*! + Sets the horizontal header item for column \a column to \a item. +*/ +void QTableWidget::setHorizontalHeaderItem(int column, QTableWidgetItem *item) +{ + Q_D(QTableWidget); + if (item) { + item->view = this; + d->model()->setHorizontalHeaderItem(column, item); + } else { + delete takeHorizontalHeaderItem(column); + } +} + +/*! + \since 4.1 + Removes the horizontal header item at \a column from the header without deleting it. +*/ +QTableWidgetItem *QTableWidget::takeHorizontalHeaderItem(int column) +{ + Q_D(QTableWidget); + QTableWidgetItem *itm = d->model()->takeHorizontalHeaderItem(column); + if (itm) + itm->view = 0; + return itm; +} + +/*! + Sets the vertical header labels using \a labels. +*/ +void QTableWidget::setVerticalHeaderLabels(const QStringList &labels) +{ + Q_D(QTableWidget); + QTableModel *model = d->model(); + QTableWidgetItem *item = 0; + for (int i = 0; i < model->rowCount() && i < labels.count(); ++i) { + item = model->verticalHeaderItem(i); + if (!item) { + item = model->createItem(); + setVerticalHeaderItem(i, item); + } + item->setText(labels.at(i)); + } +} + +/*! + Sets the horizontal header labels using \a labels. +*/ +void QTableWidget::setHorizontalHeaderLabels(const QStringList &labels) +{ + Q_D(QTableWidget); + QTableModel *model = d->model(); + QTableWidgetItem *item = 0; + for (int i = 0; i < model->columnCount() && i < labels.count(); ++i) { + item = model->horizontalHeaderItem(i); + if (!item) { + item = model->createItem(); + setHorizontalHeaderItem(i, item); + } + item->setText(labels.at(i)); + } +} + +/*! + Returns the row of the current item. + + \sa currentColumn(), setCurrentCell() +*/ +int QTableWidget::currentRow() const +{ + return currentIndex().row(); +} + +/*! + Returns the column of the current item. + + \sa currentRow(), setCurrentCell() +*/ +int QTableWidget::currentColumn() const +{ + return currentIndex().column(); +} + +/*! + Returns the current item. + + \sa setCurrentItem() +*/ +QTableWidgetItem *QTableWidget::currentItem() const +{ + Q_D(const QTableWidget); + return d->model()->item(currentIndex()); +} + +/*! + Sets the current item to \a item. + + Depending on the current \l{QAbstractItemView::SelectionMode}{selection mode}, + the item may also be selected. + + \sa currentItem(), setCurrentCell() +*/ +void QTableWidget::setCurrentItem(QTableWidgetItem *item) +{ + Q_D(QTableWidget); + setCurrentIndex(d->model()->index(item)); +} + +/*! + \since 4.4 + + Sets the current item to be \a item, using the given \a command. + + \sa currentItem(), setCurrentCell() +*/ +void QTableWidget::setCurrentItem(QTableWidgetItem *item, QItemSelectionModel::SelectionFlags command) +{ + Q_D(QTableWidget); + d->selectionModel->setCurrentIndex(d->model()->index(item), command); +} + +/*! + \since 4.1 + + Sets the current cell to be the cell at position (\a row, \a + column). + + Depending on the current \l{QAbstractItemView::SelectionMode}{selection mode}, + the cell may also be selected. + + \sa setCurrentItem(), currentRow(), currentColumn() +*/ +void QTableWidget::setCurrentCell(int row, int column) +{ + setCurrentIndex(model()->index(row, column, QModelIndex())); +} + +/*! + \since 4.4 + + Sets the current cell to be the cell at position (\a row, \a + column), using the given \a command. + + \sa setCurrentItem(), currentRow(), currentColumn() +*/ +void QTableWidget::setCurrentCell(int row, int column, QItemSelectionModel::SelectionFlags command) +{ + Q_D(QTableWidget); + d->selectionModel->setCurrentIndex(model()->index(row, column, QModelIndex()), command); +} + +/*! + Sorts all the rows in the table widget based on \a column and \a order. +*/ +void QTableWidget::sortItems(int column, Qt::SortOrder order) +{ + Q_D(QTableWidget); + d->model()->sort(column, order); + horizontalHeader()->setSortIndicator(column, order); +} + +/*! + \internal +*/ +void QTableWidget::setSortingEnabled(bool enable) +{ + QTableView::setSortingEnabled(enable); +} + +/*! + \internal +*/ +bool QTableWidget::isSortingEnabled() const +{ + return QTableView::isSortingEnabled(); +} + +/*! + Starts editing the \a item if it is editable. +*/ + +void QTableWidget::editItem(QTableWidgetItem *item) +{ + Q_D(QTableWidget); + if (!item) + return; + edit(d->model()->index(item)); +} + +/*! + Opens an editor for the give \a item. The editor remains open after editing. + + \sa closePersistentEditor() +*/ +void QTableWidget::openPersistentEditor(QTableWidgetItem *item) +{ + Q_D(QTableWidget); + if (!item) + return; + QModelIndex index = d->model()->index(item); + QAbstractItemView::openPersistentEditor(index); +} + +/*! + Closes the persistent editor for \a item. + + \sa openPersistentEditor() +*/ +void QTableWidget::closePersistentEditor(QTableWidgetItem *item) +{ + Q_D(QTableWidget); + if (!item) + return; + QModelIndex index = d->model()->index(item); + QAbstractItemView::closePersistentEditor(index); +} + +/*! + \since 4.1 + + Returns the widget displayed in the cell in the given \a row and \a column. + + \note The table takes ownership of the widget. + + \sa setCellWidget() +*/ +QWidget *QTableWidget::cellWidget(int row, int column) const +{ + QModelIndex index = model()->index(row, column, QModelIndex()); + return QAbstractItemView::indexWidget(index); +} + +/*! + \since 4.1 + + Sets the given \a widget to be displayed in the cell in the given \a row + and \a column, passing the ownership of the widget to the table. + + If cell widget A is replaced with cell widget B, cell widget A will be + deleted. For example, in the code snippet below, the QLineEdit object will + be deleted. + + \snippet doc/src/snippets/code/src_gui_itemviews_qtablewidget.cpp 0 + + \sa cellWidget() +*/ +void QTableWidget::setCellWidget(int row, int column, QWidget *widget) +{ + QModelIndex index = model()->index(row, column, QModelIndex()); + QAbstractItemView::setIndexWidget(index, widget); +} + +/*! + Returns true if the \a item is selected, otherwise returns false. + + \obsolete + + This function is deprecated. Use \l{QTableWidgetItem::isSelected()} instead. +*/ + +bool QTableWidget::isItemSelected(const QTableWidgetItem *item) const +{ + Q_D(const QTableWidget); + QModelIndex index = d->model()->index(item); + return selectionModel()->isSelected(index); +} + +/*! + Selects or deselects \a item depending on \a select. + + \obsolete + + This function is deprecated. Use \l{QTableWidgetItem::setSelected()} instead. +*/ +void QTableWidget::setItemSelected(const QTableWidgetItem *item, bool select) +{ + Q_D(QTableWidget); + QModelIndex index = d->model()->index(item); + selectionModel()->select(index, select ? QItemSelectionModel::Select : QItemSelectionModel::Deselect); +} + +/*! + Selects or deselects the \a range depending on \a select. +*/ +void QTableWidget::setRangeSelected(const QTableWidgetSelectionRange &range, bool select) +{ + if (!model()->hasIndex(range.topRow(), range.leftColumn(), rootIndex()) || + !model()->hasIndex(range.bottomRow(), range.rightColumn(), rootIndex())) + return; + + QModelIndex topLeft = model()->index(range.topRow(), range.leftColumn(), rootIndex()); + QModelIndex bottomRight = model()->index(range.bottomRow(), range.rightColumn(), rootIndex()); + + selectionModel()->select(QItemSelection(topLeft, bottomRight), + select ? QItemSelectionModel::Select : QItemSelectionModel::Deselect); +} + +/*! + Returns a list of all selected ranges. + + \sa QTableWidgetSelectionRange +*/ + +QList<QTableWidgetSelectionRange> QTableWidget::selectedRanges() const +{ + const QList<QItemSelectionRange> ranges = selectionModel()->selection(); + QList<QTableWidgetSelectionRange> result; + for (int i = 0; i < ranges.count(); ++i) + result.append(QTableWidgetSelectionRange(ranges.at(i).top(), + ranges.at(i).left(), + ranges.at(i).bottom(), + ranges.at(i).right())); + return result; +} + +/*! + Returns a list of all selected items. + + This function returns a list of pointers to the contents of the + selected cells. Use the selectedIndexes() function to retrieve the + complete selection \e including empty cells. + + \sa selectedIndexes() +*/ + +QList<QTableWidgetItem*> QTableWidget::selectedItems() +{ + Q_D(QTableWidget); + QModelIndexList indexes = selectionModel()->selectedIndexes(); + QList<QTableWidgetItem*> items; + for (int i = 0; i < indexes.count(); ++i) { + QModelIndex index = indexes.at(i); + if (isIndexHidden(index)) + continue; + QTableWidgetItem *item = d->model()->item(index); + if (item) + items.append(item); + } + return items; +} + +/*! + Finds items that matches the \a text using the given \a flags. +*/ + +QList<QTableWidgetItem*> QTableWidget::findItems(const QString &text, Qt::MatchFlags flags) const +{ + Q_D(const QTableWidget); + QModelIndexList indexes; + for (int column = 0; column < columnCount(); ++column) + indexes += d->model()->match(model()->index(0, column, QModelIndex()), + Qt::DisplayRole, text, -1, flags); + QList<QTableWidgetItem*> items; + for (int i = 0; i < indexes.size(); ++i) + items.append(d->model()->item(indexes.at(i))); + return items; +} + +/*! + Returns the visual row of the given \a logicalRow. +*/ + +int QTableWidget::visualRow(int logicalRow) const +{ + return verticalHeader()->visualIndex(logicalRow); +} + +/*! + Returns the visual column of the given \a logicalColumn. +*/ + +int QTableWidget::visualColumn(int logicalColumn) const +{ + return horizontalHeader()->visualIndex(logicalColumn); +} + +/*! + \fn QTableWidgetItem *QTableWidget::itemAt(const QPoint &point) const + + Returns a pointer to the item at the given \a point, or returns 0 if + \a point is not covered by an item in the table widget. + + \sa item() +*/ + +QTableWidgetItem *QTableWidget::itemAt(const QPoint &p) const +{ + Q_D(const QTableWidget); + return d->model()->item(indexAt(p)); +} + +/*! + Returns the rectangle on the viewport occupied by the item at \a item. +*/ +QRect QTableWidget::visualItemRect(const QTableWidgetItem *item) const +{ + Q_D(const QTableWidget); + if (!item) + return QRect(); + QModelIndex index = d->model()->index(const_cast<QTableWidgetItem*>(item)); + Q_ASSERT(index.isValid()); + return visualRect(index); +} + +/*! + Scrolls the view if necessary to ensure that the \a item is visible. + The \a hint parameter specifies more precisely where the + \a item should be located after the operation. +*/ + +void QTableWidget::scrollToItem(const QTableWidgetItem *item, QAbstractItemView::ScrollHint hint) +{ + Q_D(QTableWidget); + if (!item) + return; + QModelIndex index = d->model()->index(const_cast<QTableWidgetItem*>(item)); + Q_ASSERT(index.isValid()); + QTableView::scrollTo(index, hint); +} + +/*! + Returns the item prototype used by the table. + + \sa setItemPrototype() +*/ +const QTableWidgetItem *QTableWidget::itemPrototype() const +{ + Q_D(const QTableWidget); + return d->model()->itemPrototype(); +} + +/*! + Sets the item prototype for the table to the specified \a item. + + The table widget will use the item prototype clone function when it needs + to create a new table item. For example when the user is editing + editing in an empty cell. This is useful when you have a QTableWidgetItem + subclass and want to make sure that QTableWidget creates instances of + your subclass. + + The table takes ownership of the prototype. + + \sa itemPrototype() +*/ +void QTableWidget::setItemPrototype(const QTableWidgetItem *item) +{ + Q_D(QTableWidget); + d->model()->setItemPrototype(item); +} + +/*! + Inserts an empty row into the table at \a row. +*/ +void QTableWidget::insertRow(int row) +{ + Q_D(QTableWidget); + d->model()->insertRows(row); +} + +/*! + Inserts an empty column into the table at \a column. +*/ +void QTableWidget::insertColumn(int column) +{ + Q_D(QTableWidget); + d->model()->insertColumns(column); +} + +/*! + Removes the row \a row and all its items from the table. +*/ +void QTableWidget::removeRow(int row) +{ + Q_D(QTableWidget); + d->model()->removeRows(row); +} + +/*! + Removes the column \a column and all its items from the table. +*/ +void QTableWidget::removeColumn(int column) +{ + Q_D(QTableWidget); + d->model()->removeColumns(column); +} + +/*! + Removes all items in the view. + This will also remove all selections. + The table dimensions stay the same. +*/ + +void QTableWidget::clear() +{ + Q_D(QTableWidget); + selectionModel()->clear(); + d->model()->clear(); +} + +/*! + \since 4.2 + + Removes all items not in the headers from the view. + This will also remove all selections. + The table dimensions stay the same. +*/ +void QTableWidget::clearContents() +{ + Q_D(QTableWidget); + selectionModel()->clear(); + d->model()->clearContents(); +} + +/*! + Returns a list of MIME types that can be used to describe a list of + tablewidget items. + + \sa mimeData() +*/ +QStringList QTableWidget::mimeTypes() const +{ + return d_func()->model()->QAbstractTableModel::mimeTypes(); +} + +/*! + Returns an object that contains a serialized description of the specified + \a items. The format used to describe the items is obtained from the + mimeTypes() function. + + If the list of items is empty, 0 is returned rather than a serialized + empty list. +*/ +QMimeData *QTableWidget::mimeData(const QList<QTableWidgetItem*>) const +{ + return d_func()->model()->internalMimeData(); +} + +/*! + Handles the \a data supplied by a drag and drop operation that ended with + the given \a action in the given \a row and \a column. + Returns true if the data and action can be handled by the model; + otherwise returns false. + + \sa supportedDropActions() +*/ +bool QTableWidget::dropMimeData(int row, int column, const QMimeData *data, Qt::DropAction action) +{ + QModelIndex idx; +#ifndef QT_NO_DRAGANDDROP + if (dropIndicatorPosition() == QAbstractItemView::OnItem) { + // QAbstractTableModel::dropMimeData will overwrite on the index if row == -1 and column == -1 + idx = model()->index(row, column); + row = -1; + column = -1; + } +#endif + return d_func()->model()->QAbstractTableModel::dropMimeData(data, action , row, column, idx); +} + +/*! + Returns the drop actions supported by this view. + + \sa Qt::DropActions +*/ +Qt::DropActions QTableWidget::supportedDropActions() const +{ + return d_func()->model()->QAbstractTableModel::supportedDropActions() | Qt::MoveAction; +} + +/*! + Returns a list of pointers to the items contained in the \a data object. + If the object was not created by a QTreeWidget in the same process, the list + is empty. + +*/ +QList<QTableWidgetItem*> QTableWidget::items(const QMimeData *data) const +{ + const QTableWidgetMimeData *twd = qobject_cast<const QTableWidgetMimeData*>(data); + if (twd) + return twd->items; + return QList<QTableWidgetItem*>(); +} + +/*! + Returns the QModelIndex assocated with the given \a item. +*/ + +QModelIndex QTableWidget::indexFromItem(QTableWidgetItem *item) const +{ + Q_D(const QTableWidget); + return d->model()->index(item); +} + +/*! + Returns a pointer to the QTableWidgetItem assocated with the given \a index. +*/ + +QTableWidgetItem *QTableWidget::itemFromIndex(const QModelIndex &index) const +{ + Q_D(const QTableWidget); + return d->model()->item(index); +} + +/*! + \internal +*/ +void QTableWidget::setModel(QAbstractItemModel * /*model*/) +{ + Q_ASSERT(!"QTableWidget::setModel() - Changing the model of the QTableWidget is not allowed."); +} + +/*! \reimp */ +bool QTableWidget::event(QEvent *e) +{ + return QTableView::event(e); +} + +#ifndef QT_NO_DRAGANDDROP +/*! \reimp */ +void QTableWidget::dropEvent(QDropEvent *event) { + Q_D(QTableWidget); + if (event->source() == this && (event->dropAction() == Qt::MoveAction || + dragDropMode() == QAbstractItemView::InternalMove)) { + QModelIndex topIndex; + int col = -1; + int row = -1; + if (d->dropOn(event, &row, &col, &topIndex)) { + QModelIndexList indexes = selectedIndexes(); + int top = INT_MAX; + int left = INT_MAX; + for (int i = 0; i < indexes.count(); ++i) { + top = qMin(indexes.at(i).row(), top); + left = qMin(indexes.at(i).column(), left); + } + + QList<QTableWidgetItem *> taken; + for (int i = 0; i < indexes.count(); ++i) + taken.append(takeItem(indexes.at(i).row(), indexes.at(i).column())); + + for (int i = 0; i < indexes.count(); ++i) { + QModelIndex index = indexes.at(i); + int r = index.row() - top + topIndex.row(); + int c = index.column() - left + topIndex.column(); + setItem(r, c, taken.takeFirst()); + } + + event->accept(); + // Don't want QAbstractItemView to delete it because it was "moved" we already did it + event->setDropAction(Qt::CopyAction); + } + } + + QTableView::dropEvent(event); +} +#endif + +QT_END_NAMESPACE + +#include "moc_qtablewidget.cpp" + +#endif // QT_NO_TABLEWIDGET diff --git a/src/gui/itemviews/qtablewidget.h b/src/gui/itemviews/qtablewidget.h new file mode 100644 index 0000000..c97f6a6 --- /dev/null +++ b/src/gui/itemviews/qtablewidget.h @@ -0,0 +1,377 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTABLEWIDGET_H +#define QTABLEWIDGET_H + +#include <QtGui/qtableview.h> +#include <QtCore/qvariant.h> +#include <QtCore/qvector.h> +//#include <QtGui/qitemselectionmodel.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_TABLEWIDGET + +class Q_GUI_EXPORT QTableWidgetSelectionRange +{ +public: + QTableWidgetSelectionRange(); + QTableWidgetSelectionRange(int top, int left, int bottom, int right); + QTableWidgetSelectionRange(const QTableWidgetSelectionRange &other); + ~QTableWidgetSelectionRange(); + + inline int topRow() const { return top; } + inline int bottomRow() const { return bottom; } + inline int leftColumn() const { return left; } + inline int rightColumn() const { return right; } + inline int rowCount() const { return bottom - top + 1; } + inline int columnCount() const { return right - left + 1; } + +private: + int top, left, bottom, right; +}; + +class QTableWidget; +class QTableModel; +class QWidgetItemData; +class QTableWidgetItemPrivate; + +class Q_GUI_EXPORT QTableWidgetItem +{ + friend class QTableWidget; + friend class QTableModel; +public: + enum ItemType { Type = 0, UserType = 1000 }; + QTableWidgetItem(int type = Type); + explicit QTableWidgetItem(const QString &text, int type = Type); + explicit QTableWidgetItem(const QIcon &icon, const QString &text, int type = Type); + QTableWidgetItem(const QTableWidgetItem &other); + virtual ~QTableWidgetItem(); + + virtual QTableWidgetItem *clone() const; + + inline QTableWidget *tableWidget() const { return view; } + + inline int row() const; + inline int column() const; + + inline void setSelected(bool select); + inline bool isSelected() const; + + inline Qt::ItemFlags flags() const { return itemFlags; } + void setFlags(Qt::ItemFlags flags); + + inline QString text() const + { return data(Qt::DisplayRole).toString(); } + inline void setText(const QString &text); + + inline QIcon icon() const + { return qvariant_cast<QIcon>(data(Qt::DecorationRole)); } + inline void setIcon(const QIcon &icon); + + inline QString statusTip() const + { return data(Qt::StatusTipRole).toString(); } + inline void setStatusTip(const QString &statusTip); + +#ifndef QT_NO_TOOLTIP + inline QString toolTip() const + { return data(Qt::ToolTipRole).toString(); } + inline void setToolTip(const QString &toolTip); +#endif + +#ifndef QT_NO_WHATSTHIS + inline QString whatsThis() const + { return data(Qt::WhatsThisRole).toString(); } + inline void setWhatsThis(const QString &whatsThis); +#endif + + inline QFont font() const + { return qvariant_cast<QFont>(data(Qt::FontRole)); } + inline void setFont(const QFont &font); + + inline int textAlignment() const + { return data(Qt::TextAlignmentRole).toInt(); } + inline void setTextAlignment(int alignment) + { setData(Qt::TextAlignmentRole, alignment); } + + inline QColor backgroundColor() const + { return qvariant_cast<QColor>(data(Qt::BackgroundColorRole)); } + inline void setBackgroundColor(const QColor &color) + { setData(Qt::BackgroundColorRole, color); } + + inline QBrush background() const + { return qvariant_cast<QBrush>(data(Qt::BackgroundRole)); } + inline void setBackground(const QBrush &brush) + { setData(Qt::BackgroundRole, brush); } + + inline QColor textColor() const + { return qvariant_cast<QColor>(data(Qt::TextColorRole)); } + inline void setTextColor(const QColor &color) + { setData(Qt::TextColorRole, color); } + + inline QBrush foreground() const + { return qvariant_cast<QBrush>(data(Qt::ForegroundRole)); } + inline void setForeground(const QBrush &brush) + { setData(Qt::ForegroundRole, brush); } + + inline Qt::CheckState checkState() const + { return static_cast<Qt::CheckState>(data(Qt::CheckStateRole).toInt()); } + inline void setCheckState(Qt::CheckState state) + { setData(Qt::CheckStateRole, state); } + + inline QSize sizeHint() const + { return qvariant_cast<QSize>(data(Qt::SizeHintRole)); } + inline void setSizeHint(const QSize &size) + { setData(Qt::SizeHintRole, size); } + + virtual QVariant data(int role) const; + virtual void setData(int role, const QVariant &value); + + virtual bool operator<(const QTableWidgetItem &other) const; + +#ifndef QT_NO_DATASTREAM + virtual void read(QDataStream &in); + virtual void write(QDataStream &out) const; +#endif + QTableWidgetItem &operator=(const QTableWidgetItem &other); + + inline int type() const { return rtti; } + +private: + int rtti; + QVector<QWidgetItemData> values; + QTableWidget *view; + QTableWidgetItemPrivate *d; + Qt::ItemFlags itemFlags; +}; + +inline void QTableWidgetItem::setText(const QString &atext) +{ setData(Qt::DisplayRole, atext); } + +inline void QTableWidgetItem::setIcon(const QIcon &aicon) +{ setData(Qt::DecorationRole, aicon); } + +inline void QTableWidgetItem::setStatusTip(const QString &astatusTip) +{ setData(Qt::StatusTipRole, astatusTip); } + +#ifndef QT_NO_TOOLTIP +inline void QTableWidgetItem::setToolTip(const QString &atoolTip) +{ setData(Qt::ToolTipRole, atoolTip); } +#endif + +#ifndef QT_NO_WHATSTHIS +inline void QTableWidgetItem::setWhatsThis(const QString &awhatsThis) +{ setData(Qt::WhatsThisRole, awhatsThis); } +#endif + +inline void QTableWidgetItem::setFont(const QFont &afont) +{ setData(Qt::FontRole, afont); } + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &in, QTableWidgetItem &item); +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &out, const QTableWidgetItem &item); +#endif + +class QTableWidgetPrivate; + +class Q_GUI_EXPORT QTableWidget : public QTableView +{ + Q_OBJECT + Q_PROPERTY(int rowCount READ rowCount WRITE setRowCount) + Q_PROPERTY(int columnCount READ columnCount WRITE setColumnCount) + + friend class QTableModel; +public: + explicit QTableWidget(QWidget *parent = 0); + QTableWidget(int rows, int columns, QWidget *parent = 0); + ~QTableWidget(); + + void setRowCount(int rows); + int rowCount() const; + + void setColumnCount(int columns); + int columnCount() const; + + int row(const QTableWidgetItem *item) const; + int column(const QTableWidgetItem *item) const; + + QTableWidgetItem *item(int row, int column) const; + void setItem(int row, int column, QTableWidgetItem *item); + QTableWidgetItem *takeItem(int row, int column); + + QTableWidgetItem *verticalHeaderItem(int row) const; + void setVerticalHeaderItem(int row, QTableWidgetItem *item); + QTableWidgetItem *takeVerticalHeaderItem(int row); + + QTableWidgetItem *horizontalHeaderItem(int column) const; + void setHorizontalHeaderItem(int column, QTableWidgetItem *item); + QTableWidgetItem *takeHorizontalHeaderItem(int column); + void setVerticalHeaderLabels(const QStringList &labels); + void setHorizontalHeaderLabels(const QStringList &labels); + + int currentRow() const; + int currentColumn() const; + QTableWidgetItem *currentItem() const; + void setCurrentItem(QTableWidgetItem *item); + void setCurrentItem(QTableWidgetItem *item, QItemSelectionModel::SelectionFlags command); + void setCurrentCell(int row, int column); + void setCurrentCell(int row, int column, QItemSelectionModel::SelectionFlags command); + + void sortItems(int column, Qt::SortOrder order = Qt::AscendingOrder); + void setSortingEnabled(bool enable); + bool isSortingEnabled() const; + + void editItem(QTableWidgetItem *item); + void openPersistentEditor(QTableWidgetItem *item); + void closePersistentEditor(QTableWidgetItem *item); + + QWidget *cellWidget(int row, int column) const; + void setCellWidget(int row, int column, QWidget *widget); + inline void removeCellWidget(int row, int column); + + bool isItemSelected(const QTableWidgetItem *item) const; + void setItemSelected(const QTableWidgetItem *item, bool select); + void setRangeSelected(const QTableWidgetSelectionRange &range, bool select); + + QList<QTableWidgetSelectionRange> selectedRanges() const; + QList<QTableWidgetItem*> selectedItems(); + QList<QTableWidgetItem*> findItems(const QString &text, Qt::MatchFlags flags) const; + + int visualRow(int logicalRow) const; + int visualColumn(int logicalColumn) const; + + QTableWidgetItem *itemAt(const QPoint &p) const; + inline QTableWidgetItem *itemAt(int x, int y) const; + QRect visualItemRect(const QTableWidgetItem *item) const; + + const QTableWidgetItem *itemPrototype() const; + void setItemPrototype(const QTableWidgetItem *item); + +public Q_SLOTS: + void scrollToItem(const QTableWidgetItem *item, QAbstractItemView::ScrollHint hint = EnsureVisible); + void insertRow(int row); + void insertColumn(int column); + void removeRow(int row); + void removeColumn(int column); + void clear(); + void clearContents(); + +Q_SIGNALS: + void itemPressed(QTableWidgetItem *item); + void itemClicked(QTableWidgetItem *item); + void itemDoubleClicked(QTableWidgetItem *item); + + void itemActivated(QTableWidgetItem *item); + void itemEntered(QTableWidgetItem *item); + void itemChanged(QTableWidgetItem *item); + + void currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous); + void itemSelectionChanged(); + + void cellPressed(int row, int column); + void cellClicked(int row, int column); + void cellDoubleClicked(int row, int column); + + void cellActivated(int row, int column); + void cellEntered(int row, int column); + void cellChanged(int row, int column); + + void currentCellChanged(int currentRow, int currentColumn, int previousRow, int previousColumn); + +protected: + bool event(QEvent *e); + virtual QStringList mimeTypes() const; + virtual QMimeData *mimeData(const QList<QTableWidgetItem*> items) const; + virtual bool dropMimeData(int row, int column, const QMimeData *data, Qt::DropAction action); + virtual Qt::DropActions supportedDropActions() const; + QList<QTableWidgetItem*> items(const QMimeData *data) const; + + QModelIndex indexFromItem(QTableWidgetItem *item) const; + QTableWidgetItem *itemFromIndex(const QModelIndex &index) const; + void dropEvent(QDropEvent *event); + +private: + void setModel(QAbstractItemModel *model); + + Q_DECLARE_PRIVATE(QTableWidget) + Q_DISABLE_COPY(QTableWidget) + + Q_PRIVATE_SLOT(d_func(), void _q_emitItemPressed(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemClicked(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemDoubleClicked(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemActivated(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemEntered(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemChanged(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitCurrentItemChanged(const QModelIndex &previous, const QModelIndex ¤t)) + Q_PRIVATE_SLOT(d_func(), void _q_sort()) + Q_PRIVATE_SLOT(d_func(), void _q_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)) +}; + +inline void QTableWidget::removeCellWidget(int arow, int acolumn) +{ setCellWidget(arow, acolumn, 0); } + +inline QTableWidgetItem *QTableWidget::itemAt(int ax, int ay) const +{ return itemAt(QPoint(ax, ay)); } + +inline int QTableWidgetItem::row() const +{ return (view ? view->row(this) : -1); } + +inline int QTableWidgetItem::column() const +{ return (view ? view->column(this) : -1); } + +inline void QTableWidgetItem::setSelected(bool aselect) +{ if (view) view->setItemSelected(this, aselect); } + +inline bool QTableWidgetItem::isSelected() const +{ return (view ? view->isItemSelected(this) : false); } + +#endif // QT_NO_TABLEWIDGET + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTABLEWIDGET_H diff --git a/src/gui/itemviews/qtablewidget_p.h b/src/gui/itemviews/qtablewidget_p.h new file mode 100644 index 0000000..2e1dab6 --- /dev/null +++ b/src/gui/itemviews/qtablewidget_p.h @@ -0,0 +1,223 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTABLEWIDGET_P_H +#define QTABLEWIDGET_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. This header file may change +// from version to version without notice, or even be removed. +// +// We mean it. +// + +#include <qheaderview.h> +#include <qtablewidget.h> +#include <qabstractitemmodel.h> +#include <private/qabstractitemmodel_p.h> +#include <private/qtableview_p.h> +#include <private/qwidgetitemdata_p.h> + +#ifndef QT_NO_TABLEWIDGET + +QT_BEGIN_NAMESPACE + +// workaround for VC++ 6.0 linker bug +typedef bool(*LessThan)(const QPair<QTableWidgetItem*,int>&,const QPair<QTableWidgetItem*,int>&); + +class QTableWidgetMimeData : public QMimeData +{ + Q_OBJECT +public: + QList<QTableWidgetItem*> items; +}; + +class QTableModelLessThan +{ +public: + inline bool operator()(QTableWidgetItem *i1, QTableWidgetItem *i2) const + { return (*i1 < *i2); } +}; + +class QTableModelGreaterThan +{ +public: + inline bool operator()(QTableWidgetItem *i1, QTableWidgetItem *i2) const + { return (*i2 < *i1); } +}; + +class QTableModel : public QAbstractTableModel +{ + Q_OBJECT +public: + enum ItemFlagsExtension { + ItemIsHeaderItem = 128 + }; // we need this to separate header items from other items + + QTableModel(int rows, int columns, QTableWidget *parent); + ~QTableModel(); + + bool insertRows(int row, int count = 1, const QModelIndex &parent = QModelIndex()); + bool insertColumns(int column, int count = 1, const QModelIndex &parent = QModelIndex()); + + bool removeRows(int row, int count = 1, const QModelIndex &parent = QModelIndex()); + bool removeColumns(int column, int count = 1, const QModelIndex &parent = QModelIndex()); + + void setItem(int row, int column, QTableWidgetItem *item); + QTableWidgetItem *takeItem(int row, int column); + QTableWidgetItem *item(int row, int column) const; + QTableWidgetItem *item(const QModelIndex &index) const; + void removeItem(QTableWidgetItem *item); + + void setHorizontalHeaderItem(int section, QTableWidgetItem *item); + void setVerticalHeaderItem(int section, QTableWidgetItem *item); + QTableWidgetItem *takeHorizontalHeaderItem(int section); + QTableWidgetItem *takeVerticalHeaderItem(int section); + QTableWidgetItem *horizontalHeaderItem(int section); + QTableWidgetItem *verticalHeaderItem(int section); + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const + { return QAbstractTableModel::index(row, column, parent); } + + QModelIndex index(const QTableWidgetItem *item) const; + + void setRowCount(int rows); + void setColumnCount(int columns); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex &index, const QVariant &value, int role); + bool setItemData(const QModelIndex &index, const QMap<int, QVariant> &roles); + + QMap<int, QVariant> itemData(const QModelIndex &index) const; + + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role); + + Qt::ItemFlags flags(const QModelIndex &index) const; + + void sort(int column, Qt::SortOrder order); + static bool itemLessThan(const QPair<QTableWidgetItem*,int> &left, + const QPair<QTableWidgetItem*,int> &right); + static bool itemGreaterThan(const QPair<QTableWidgetItem*,int> &left, + const QPair<QTableWidgetItem*,int> &right); + static bool canConvertToDouble(const QVariant &value); + + void ensureSorted(int column, Qt::SortOrder order, int start, int end); + QVector<QTableWidgetItem*> columnItems(int column) const; + void updateRowIndexes(QModelIndexList &indexes, int movedFromRow, int movedToRow); + static QVector<QTableWidgetItem*>::iterator sortedInsertionIterator( + const QVector<QTableWidgetItem*>::iterator &begin, + const QVector<QTableWidgetItem*>::iterator &end, + Qt::SortOrder order, QTableWidgetItem *item); + + bool isValid(const QModelIndex &index) const; + inline long tableIndex(int row, int column) const + { return (row * horizontalHeaderItems.count()) + column; } + + void clear(); + void clearContents(); + void itemChanged(QTableWidgetItem *item); + + QTableWidgetItem *createItem() const; + const QTableWidgetItem *itemPrototype() const; + void setItemPrototype(const QTableWidgetItem *item); + + // dnd + QStringList mimeTypes() const; + QMimeData *mimeData(const QModelIndexList &indexes) const; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent); + Qt::DropActions supportedDropActions() const; + + QMimeData *internalMimeData() const; + +private: + const QTableWidgetItem *prototype; + QVector<QTableWidgetItem*> tableItems; + QVector<QTableWidgetItem*> verticalHeaderItems; + QVector<QTableWidgetItem*> horizontalHeaderItems; + + // A cache must be mutable if get-functions should have const modifiers + mutable QModelIndexList cachedIndexes; +}; + +class QTableWidgetPrivate : public QTableViewPrivate +{ + Q_DECLARE_PUBLIC(QTableWidget) +public: + QTableWidgetPrivate() : QTableViewPrivate() {} + inline QTableModel *model() const { return qobject_cast<QTableModel*>(q_func()->model()); } + void setup(); + + // view signals + void _q_emitItemPressed(const QModelIndex &index); + void _q_emitItemClicked(const QModelIndex &index); + void _q_emitItemDoubleClicked(const QModelIndex &index); + void _q_emitItemActivated(const QModelIndex &index); + void _q_emitItemEntered(const QModelIndex &index); + // model signals + void _q_emitItemChanged(const QModelIndex &index); + // selection signals + void _q_emitCurrentItemChanged(const QModelIndex &previous, const QModelIndex ¤t); + // sorting + void _q_sort(); + void _q_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); +}; + +class QTableWidgetItemPrivate +{ +public: + QTableWidgetItemPrivate(QTableWidgetItem *item) : q(item), id(-1) {} + QTableWidgetItem *q; + int id; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_TABLEWIDGET + +#endif // QTABLEWIDGET_P_H diff --git a/src/gui/itemviews/qtreeview.cpp b/src/gui/itemviews/qtreeview.cpp new file mode 100644 index 0000000..943b194 --- /dev/null +++ b/src/gui/itemviews/qtreeview.cpp @@ -0,0 +1,3840 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qtreeview.h" + +#ifndef QT_NO_TREEVIEW +#include <qheaderview.h> +#include <qitemdelegate.h> +#include <qapplication.h> +#include <qscrollbar.h> +#include <qpainter.h> +#include <qstack.h> +#include <qstyle.h> +#include <qstyleoption.h> +#include <qevent.h> +#include <qpen.h> +#include <qdebug.h> +#ifndef QT_NO_ACCESSIBILITY +#include <qaccessible.h> +#endif + +#include <private/qtreeview_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QTreeView + \brief The QTreeView class provides a default model/view implementation of a tree view. + + \ingroup model-view + \ingroup advanced + \mainclass + + A QTreeView implements a tree representation of items from a + model. This class is used to provide standard hierarchical lists that + were previously provided by the \c QListView class, but using the more + flexible approach provided by Qt's model/view architecture. + + The QTreeView class is one of the \l{Model/View Classes} and is part of + Qt's \l{Model/View Programming}{model/view framework}. + + QTreeView implements the interfaces defined by the + QAbstractItemView class to allow it to display data provided by + models derived from the QAbstractItemModel class. + + It is simple to construct a tree view displaying data from a + model. In the following example, the contents of a directory are + supplied by a QDirModel and displayed as a tree: + + \snippet doc/src/snippets/shareddirmodel/main.cpp 3 + \snippet doc/src/snippets/shareddirmodel/main.cpp 6 + + The model/view architecture ensures that the contents of the tree view + are updated as the model changes. + + Items that have children can be in an expanded (children are + visible) or collapsed (children are hidden) state. When this state + changes a collapsed() or expanded() signal is emitted with the + model index of the relevant item. + + The amount of indentation used to indicate levels of hierarchy is + controlled by the \l indentation property. + + Headers in tree views are constructed using the QHeaderView class and can + be hidden using \c{header()->hide()}. Note that each header is configured + with its \l{QHeaderView::}{stretchLastSection} property set to true, + ensuring that the view does not waste any of the space assigned to it for + its header. If this value is set to true, this property will override the + resize mode set on the last section in the header. + + + \section1 Key Bindings + + QTreeView supports a set of key bindings that enable the user to + navigate in the view and interact with the contents of items: + + \table + \header \o Key \o Action + \row \o Up \o Moves the cursor to the item in the same column on + the previous row. If the parent of the current item has no more rows to + navigate to, the cursor moves to the relevant item in the last row + of the sibling that precedes the parent. + \row \o Down \o Moves the cursor to the item in the same column on + the next row. If the parent of the current item has no more rows to + navigate to, the cursor moves to the relevant item in the first row + of the sibling that follows the parent. + \row \o Left \o Hides the children of the current item (if present) + by collapsing a branch. + \row \o Minus \o Same as LeftArrow. + \row \o Right \o Reveals the children of the current item (if present) + by expanding a branch. + \row \o Plus \o Same as RightArrow. + \row \o Asterisk \o Expands all children of the current item (if present). + \row \o PageUp \o Moves the cursor up one page. + \row \o PageDown \o Moves the cursor down one page. + \row \o Home \o Moves the cursor to an item in the same column of the first + row of the first top-level item in the model. + \row \o End \o Moves the cursor to an item in the same column of the last + row of the last top-level item in the model. + \row \o F2 \o In editable models, this opens the current item for editing. + The Escape key can be used to cancel the editing process and revert + any changes to the data displayed. + \endtable + + \omit + Describe the expanding/collapsing concept if not covered elsewhere. + \endomit + + \table 100% + \row \o \inlineimage windowsxp-treeview.png Screenshot of a Windows XP style tree view + \o \inlineimage macintosh-treeview.png Screenshot of a Macintosh style tree view + \o \inlineimage plastique-treeview.png Screenshot of a Plastique style tree view + \row \o A \l{Windows XP Style Widget Gallery}{Windows XP style} tree view. + \o A \l{Macintosh Style Widget Gallery}{Macintosh style} tree view. + \o A \l{Plastique Style Widget Gallery}{Plastique style} tree view. + \endtable + + \section1 Improving Performance + + It is possible to give the view hints about the data it is handling in order + to improve its performance when displaying large numbers of items. One approach + that can be taken for views that are intended to display items with equal heights + is to set the \l uniformRowHeights property to true. + + \sa QListView, QTreeWidget, {View Classes}, QAbstractItemModel, QAbstractItemView, + {Dir View Example} +*/ + + +/*! + \fn void QTreeView::expanded(const QModelIndex &index) + + This signal is emitted when the item specified by \a index is expanded. +*/ + + +/*! + \fn void QTreeView::collapsed(const QModelIndex &index) + + This signal is emitted when the item specified by \a index is collapsed. +*/ + +/*! + Constructs a table view with a \a parent to represent a model's + data. Use setModel() to set the model. + + \sa QAbstractItemModel +*/ +QTreeView::QTreeView(QWidget *parent) + : QAbstractItemView(*new QTreeViewPrivate, parent) +{ + Q_D(QTreeView); + d->initialize(); +} + +/*! + \internal +*/ +QTreeView::QTreeView(QTreeViewPrivate &dd, QWidget *parent) + : QAbstractItemView(dd, parent) +{ + Q_D(QTreeView); + d->initialize(); +} + +/*! + Destroys the tree view. +*/ +QTreeView::~QTreeView() +{ +} + +/*! + \reimp +*/ +void QTreeView::setModel(QAbstractItemModel *model) +{ + Q_D(QTreeView); + if (model == d->model) + return; + if (d->selectionModel) { // support row editing + disconnect(d->selectionModel, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), + d->model, SLOT(submit())); + disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(rowsRemoved(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(modelAboutToBeReset()), this, SLOT(_q_modelAboutToBeReset())); + } + d->viewItems.clear(); + d->expandedIndexes.clear(); + d->hiddenIndexes.clear(); + d->header->setModel(model); + QAbstractItemView::setModel(model); + + // QAbstractItemView connects to a private slot + disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_rowsRemoved(QModelIndex,int,int))); + // do header layout after the tree + disconnect(d->model, SIGNAL(layoutChanged()), + d->header, SLOT(_q_layoutChanged())); + // QTreeView has a public slot for this + connect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(rowsRemoved(QModelIndex,int,int))); + + connect(d->model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_columnsAboutToBeRemoved(QModelIndex,int,int))); + connect(d->model, SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(_q_columnsRemoved(QModelIndex,int,int))); + + connect(d->model, SIGNAL(modelAboutToBeReset()), SLOT(_q_modelAboutToBeReset())); + + if (d->sortingEnabled) + sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder()); +} + +/*! + \reimp +*/ +void QTreeView::setRootIndex(const QModelIndex &index) +{ + Q_D(QTreeView); + d->header->setRootIndex(index); + QAbstractItemView::setRootIndex(index); +} + +/*! + \reimp +*/ +void QTreeView::setSelectionModel(QItemSelectionModel *selectionModel) +{ + Q_D(QTreeView); + Q_ASSERT(selectionModel); + if (d->selectionModel) { + if (d->allColumnsShowFocus) { + QObject::disconnect(d->selectionModel, SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_currentChanged(QModelIndex,QModelIndex))); + } + // support row editing + disconnect(d->selectionModel, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), + d->model, SLOT(submit())); + } + + d->header->setSelectionModel(selectionModel); + QAbstractItemView::setSelectionModel(selectionModel); + + if (d->selectionModel) { + if (d->allColumnsShowFocus) { + QObject::connect(d->selectionModel, SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_currentChanged(QModelIndex,QModelIndex))); + } + // support row editing + connect(d->selectionModel, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), + d->model, SLOT(submit())); + } +} + +/*! + Returns the header for the tree view. + + \sa QAbstractItemModel::headerData() +*/ +QHeaderView *QTreeView::header() const +{ + Q_D(const QTreeView); + return d->header; +} + +/*! + Sets the header for the tree view, to the given \a header. + + The view takes ownership over the given \a header and deletes it + when a new header is set. + + \sa QAbstractItemModel::headerData() +*/ +void QTreeView::setHeader(QHeaderView *header) +{ + Q_D(QTreeView); + if (header == d->header || !header) + return; + if (d->header && d->header->parent() == this) + delete d->header; + d->header = header; + d->header->setParent(this); + + if (!d->header->model()) { + d->header->setModel(d->model); + if (d->selectionModel) + d->header->setSelectionModel(d->selectionModel); + } + + connect(d->header, SIGNAL(sectionResized(int,int,int)), + this, SLOT(columnResized(int,int,int))); + connect(d->header, SIGNAL(sectionMoved(int,int,int)), + this, SLOT(columnMoved())); + connect(d->header, SIGNAL(sectionCountChanged(int,int)), + this, SLOT(columnCountChanged(int,int))); + connect(d->header, SIGNAL(sectionHandleDoubleClicked(int)), + this, SLOT(resizeColumnToContents(int))); + connect(d->header, SIGNAL(geometriesChanged()), + this, SLOT(updateGeometries())); + + setSortingEnabled(d->sortingEnabled); +} + +/*! + \property QTreeView::autoExpandDelay + \brief The delay time before items in a tree are opened during a drag and drop operation. + \since 4.3 + + This property holds the amount of time in milliseconds that the user must wait over + a node before that node will automatically open or close. If the time is + set to less then 0 then it will not be activated. + + By default, this property has a value of -1, meaning that auto-expansion is disabled. +*/ +int QTreeView::autoExpandDelay() const +{ + Q_D(const QTreeView); + return d->autoExpandDelay; +} + +void QTreeView::setAutoExpandDelay(int delay) +{ + Q_D(QTreeView); + d->autoExpandDelay = delay; +} + +/*! + \property QTreeView::indentation + \brief indentation of the items in the tree view. + + This property holds the indentation measured in pixels of the items for each + level in the tree view. For top-level items, the indentation specifies the + horizontal distance from the viewport edge to the items in the first column; + for child items, it specifies their indentation from their parent items. + + By default, this property has a value of 20. +*/ +int QTreeView::indentation() const +{ + Q_D(const QTreeView); + return d->indent; +} + +void QTreeView::setIndentation(int i) +{ + Q_D(QTreeView); + if (i != d->indent) { + d->indent = i; + d->viewport->update(); + } +} + +/*! + \property QTreeView::rootIsDecorated + \brief whether to show controls for expanding and collapsing top-level items + + Items with children are typically shown with controls to expand and collapse + them, allowing their children to be shown or hidden. If this property is + false, these controls are not shown for top-level items. This can be used to + make a single level tree structure appear like a simple list of items. + + By default, this property is true. +*/ +bool QTreeView::rootIsDecorated() const +{ + Q_D(const QTreeView); + return d->rootDecoration; +} + +void QTreeView::setRootIsDecorated(bool show) +{ + Q_D(QTreeView); + if (show != d->rootDecoration) { + d->rootDecoration = show; + d->viewport->update(); + } +} + +/*! + \property QTreeView::uniformRowHeights + \brief whether all items in the treeview have the same height + + This property should only be set to true if it is guaranteed that all items + in the view has the same height. This enables the view to do some + optimizations. + + The height is obtained from the first item in the view. It is updated + when the data changes on that item. + + By default, this property is false. +*/ +bool QTreeView::uniformRowHeights() const +{ + Q_D(const QTreeView); + return d->uniformRowHeights; +} + +void QTreeView::setUniformRowHeights(bool uniform) +{ + Q_D(QTreeView); + d->uniformRowHeights = uniform; +} + +/*! + \property QTreeView::itemsExpandable + \brief whether the items are expandable by the user. + + This property holds whether the user can expand and collapse items + interactively. + + By default, this property is true. + +*/ +bool QTreeView::itemsExpandable() const +{ + Q_D(const QTreeView); + return d->itemsExpandable; +} + +void QTreeView::setItemsExpandable(bool enable) +{ + Q_D(QTreeView); + d->itemsExpandable = enable; +} + +/*! + \property QTreeView::expandsOnDoubleClick + \since 4.4 + \brief whether the items can be expanded by double-clicking. + + This property holds whether the user can expand and collapse items + by double-clicking. The default value is true. + + \sa itemsExpandable +*/ +bool QTreeView::expandsOnDoubleClick() const +{ + Q_D(const QTreeView); + return d->expandsOnDoubleClick; +} + +void QTreeView::setExpandsOnDoubleClick(bool enable) +{ + Q_D(QTreeView); + d->expandsOnDoubleClick = enable; +} + +/*! + Returns the horizontal position of the \a column in the viewport. +*/ +int QTreeView::columnViewportPosition(int column) const +{ + Q_D(const QTreeView); + return d->header->sectionViewportPosition(column); +} + +/*! + Returns the width of the \a column. + + \sa resizeColumnToContents(), setColumnWidth() +*/ +int QTreeView::columnWidth(int column) const +{ + Q_D(const QTreeView); + return d->header->sectionSize(column); +} + +/*! + \since 4.2 + + Sets the width of the given \a column to the \a width specified. + + \sa columnWidth(), resizeColumnToContents() +*/ +void QTreeView::setColumnWidth(int column, int width) +{ + Q_D(QTreeView); + d->header->resizeSection(column, width); +} + +/*! + Returns the column in the tree view whose header covers the \a x + coordinate given. +*/ +int QTreeView::columnAt(int x) const +{ + Q_D(const QTreeView); + return d->header->logicalIndexAt(x); +} + +/*! + Returns true if the \a column is hidden; otherwise returns false. + + \sa hideColumn(), isRowHidden() +*/ +bool QTreeView::isColumnHidden(int column) const +{ + Q_D(const QTreeView); + return d->header->isSectionHidden(column); +} + +/*! + If \a hide is true the \a column is hidden, otherwise the \a column is shown. + + \sa hideColumn(), setRowHidden() +*/ +void QTreeView::setColumnHidden(int column, bool hide) +{ + Q_D(QTreeView); + if (column < 0 || column >= d->header->count()) + return; + d->header->setSectionHidden(column, hide); +} + +/*! + \property QTreeView::headerHidden + \brief whether the header is shown or not. + \since 4.4 + + If this property is true, the header is not shown otherwise it is. + The default value is false. + + \sa header() +*/ +bool QTreeView::isHeaderHidden() const +{ + Q_D(const QTreeView); + return d->header->isHidden(); +} + +void QTreeView::setHeaderHidden(bool hide) +{ + Q_D(QTreeView); + d->header->setHidden(hide); +} + +/*! + Returns true if the item in the given \a row of the \a parent is hidden; + otherwise returns false. + + \sa setRowHidden(), isColumnHidden() +*/ +bool QTreeView::isRowHidden(int row, const QModelIndex &parent) const +{ + Q_D(const QTreeView); + if (!d->model) + return false; + return d->isRowHidden(d->model->index(row, 0, parent)); +} + +/*! + If \a hide is true the \a row with the given \a parent is hidden, otherwise the \a row is shown. + + \sa isRowHidden(), setColumnHidden() +*/ +void QTreeView::setRowHidden(int row, const QModelIndex &parent, bool hide) +{ + Q_D(QTreeView); + if (!d->model) + return; + QModelIndex index = d->model->index(row, 0, parent); + if (!index.isValid()) + return; + + if (hide) { + d->hiddenIndexes.insert(index); + } else if(d->isPersistent(index)) { //if the index is not persistent, it cannot be in the set + d->hiddenIndexes.remove(index); + } + + d->doDelayedItemsLayout(); +} + +/*! + \since 4.3 + + Returns true if the item in first column in the given \a row + of the \a parent is spanning all the columns; otherwise returns false. + + \sa setFirstColumnSpanned() +*/ +bool QTreeView::isFirstColumnSpanned(int row, const QModelIndex &parent) const +{ + Q_D(const QTreeView); + if (d->spanningIndexes.isEmpty() || !d->model) + return false; + QModelIndex index = d->model->index(row, 0, parent); + for (int i = 0; i < d->spanningIndexes.count(); ++i) + if (d->spanningIndexes.at(i) == index) + return true; + return false; +} + +/*! + \since 4.3 + + If \a span is true the item in the first column in the \a row + with the given \a parent is set to span all columns, otherwise all items + on the \a row are shown. + + \sa isFirstColumnSpanned() +*/ +void QTreeView::setFirstColumnSpanned(int row, const QModelIndex &parent, bool span) +{ + Q_D(QTreeView); + if (!d->model) + return; + QModelIndex index = d->model->index(row, 0, parent); + if (!index.isValid()) + return; + + if (span) { + QPersistentModelIndex persistent(index); + if (!d->spanningIndexes.contains(persistent)) + d->spanningIndexes.append(persistent); + } else { + QPersistentModelIndex persistent(index); + int i = d->spanningIndexes.indexOf(persistent); + if (i >= 0) + d->spanningIndexes.remove(i); + } + + d->executePostedLayout(); + int i = d->viewIndex(index); + if (i >= 0) + d->viewItems[i].spanning = span; + + d->viewport->update(); +} + +/*! + \reimp +*/ +void QTreeView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + Q_D(QTreeView); + + // if we are going to do a complete relayout anyway, there is no need to update + if (d->delayedPendingLayout) + return; + + // refresh the height cache here; we don't really lose anything by getting the size hint, + // since QAbstractItemView::dataChanged() will get the visualRect for the items anyway + + QModelIndex top = topLeft.sibling(topLeft.row(), 0); + int topViewIndex = d->viewIndex(top); + if (topViewIndex == 0) + d->defaultItemHeight = indexRowSizeHint(top); + bool sizeChanged = false; + if (topViewIndex != -1) { + if (topLeft == bottomRight) { + int oldHeight = d->itemHeight(topViewIndex); + d->invalidateHeightCache(topViewIndex); + sizeChanged = (oldHeight != d->itemHeight(topViewIndex)); + } else { + QModelIndex bottom = bottomRight.sibling(bottomRight.row(), 0); + int bottomViewIndex = d->viewIndex(bottom); + for (int i = topViewIndex; i <= bottomViewIndex; ++i) { + int oldHeight = d->itemHeight(i); + d->invalidateHeightCache(i); + sizeChanged |= (oldHeight != d->itemHeight(i)); + } + } + } + + if (sizeChanged) { + d->updateScrollBars(); + d->viewport->update(); + } + QAbstractItemView::dataChanged(topLeft, bottomRight); +} + +/*! + Hides the \a column given. + + \note This function should only be called after the model has been + initialized, as the view needs to know the number of columns in order to + hide \a column. + + \sa showColumn(), setColumnHidden() +*/ +void QTreeView::hideColumn(int column) +{ + Q_D(QTreeView); + d->header->hideSection(column); +} + +/*! + Shows the given \a column in the tree view. + + \sa hideColumn(), setColumnHidden() +*/ +void QTreeView::showColumn(int column) +{ + Q_D(QTreeView); + d->header->showSection(column); +} + +/*! + \fn void QTreeView::expand(const QModelIndex &index) + + Expands the model item specified by the \a index. + + \sa expanded() +*/ +void QTreeView::expand(const QModelIndex &index) +{ + Q_D(QTreeView); + if (!d->isIndexValid(index)) + return; + if (d->delayedPendingLayout) { + //A complete relayout is going to be performed, just store the expanded index, no need to layout. + if (d->storeExpanded(index)) + emit expanded(index); + return; + } + + int i = d->viewIndex(index); + if (i != -1) { // is visible + d->expand(i, true); + if (!d->isAnimating()) { + updateGeometries(); + d->viewport->update(); + } + } else if (d->storeExpanded(index)) { + emit expanded(index); + } +} + +/*! + \fn void QTreeView::collapse(const QModelIndex &index) + + Collapses the model item specified by the \a index. + + \sa collapsed() +*/ +void QTreeView::collapse(const QModelIndex &index) +{ + Q_D(QTreeView); + if (!d->isIndexValid(index)) + return; + //if the current item is now invisible, the autoscroll will expand the tree to see it, so disable the autoscroll + d->delayedAutoScroll.stop(); + + if (d->delayedPendingLayout) { + //A complete relayout is going to be performed, just un-store the expanded index, no need to layout. + if (d->isPersistent(index) && d->expandedIndexes.remove(index)) + emit collapsed(index); + return; + } + int i = d->viewIndex(index); + if (i != -1) { // is visible + d->collapse(i, true); + if (!d->isAnimating()) { + updateGeometries(); + viewport()->update(); + } + } else { + if (d->isPersistent(index) && d->expandedIndexes.remove(index)) + emit collapsed(index); + } +} + +/*! + \fn bool QTreeView::isExpanded(const QModelIndex &index) const + + Returns true if the model item \a index is expanded; otherwise returns + false. + + \sa expand(), expanded(), setExpanded() +*/ +bool QTreeView::isExpanded(const QModelIndex &index) const +{ + Q_D(const QTreeView); + return d->isIndexExpanded(index); +} + +/*! + Sets the item referred to by \a index to either collapse or expanded, + depending on the value of \a expanded. + + \sa expanded(), expand(), isExpanded() +*/ +void QTreeView::setExpanded(const QModelIndex &index, bool expanded) +{ + if (expanded) + this->expand(index); + else + this->collapse(index); +} + +/*! + \since 4.2 + \property QTreeView::sortingEnabled + \brief whether sorting is enabled + + If this property is true, sorting is enabled for the tree; if the property + is false, sorting is not enabled. The default value is false. + + \note In order to avoid performance issues, it is recommended that + sorting is enabled \e after inserting the items into the tree. + Alternatively, you could also insert the items into a list before inserting + the items into the tree. + + \sa sortByColumn() +*/ + +void QTreeView::setSortingEnabled(bool enable) +{ + Q_D(QTreeView); + d->sortingEnabled = enable; + header()->setSortIndicatorShown(enable); + header()->setClickable(enable); + if (enable) { + connect(header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), + this, SLOT(_q_sortIndicatorChanged(int, Qt::SortOrder))); + sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder()); + } else { + disconnect(header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), + this, SLOT(_q_sortIndicatorChanged(int, Qt::SortOrder))); + } +} + +bool QTreeView::isSortingEnabled() const +{ + Q_D(const QTreeView); + return d->sortingEnabled; +} + +/*! + \since 4.2 + \property QTreeView::animated + \brief whether animations are enabled + + If this property is true the treeview will animate expandsion + and collasping of branches. If this property is false, the treeview + will expand or collapse branches immediately without showing + the animation. + + By default, this property is false. +*/ + +void QTreeView::setAnimated(bool animate) +{ + Q_D(QTreeView); + d->animationsEnabled = animate; +} + +bool QTreeView::isAnimated() const +{ + Q_D(const QTreeView); + return d->animationsEnabled; +} + +/*! + \since 4.2 + \property QTreeView::allColumnsShowFocus + \brief whether items should show keyboard focus using all columns + + If this property is true all columns will show focus, otherwise only + one column will show focus. + + The default is false. +*/ + +void QTreeView::setAllColumnsShowFocus(bool enable) +{ + Q_D(QTreeView); + if (d->allColumnsShowFocus == enable) + return; + if (d->selectionModel) { + if (enable) { + QObject::connect(d->selectionModel, SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_currentChanged(QModelIndex,QModelIndex))); + } else { + QObject::disconnect(d->selectionModel, SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_currentChanged(QModelIndex,QModelIndex))); + } + } + d->allColumnsShowFocus = enable; + d->viewport->update(); +} + +bool QTreeView::allColumnsShowFocus() const +{ + Q_D(const QTreeView); + return d->allColumnsShowFocus; +} + +/*! + \property QTreeView::wordWrap + \brief the item text word-wrapping policy + \since 4.3 + + If this property is true then the item text is wrapped where + necessary at word-breaks; otherwise it is not wrapped at all. + This property is false by default. + + Note that even if wrapping is enabled, the cell will not be + expanded to fit all text. Ellipsis will be inserted according to + the current \l{QAbstractItemView::}{textElideMode}. +*/ +void QTreeView::setWordWrap(bool on) +{ + Q_D(QTreeView); + if (d->wrapItemText == on) + return; + d->wrapItemText = on; + d->doDelayedItemsLayout(); +} + +bool QTreeView::wordWrap() const +{ + Q_D(const QTreeView); + return d->wrapItemText; +} + + +/*! + \reimp + */ +void QTreeView::keyboardSearch(const QString &search) +{ + Q_D(QTreeView); + if (!d->model->rowCount(d->root) || !d->model->columnCount(d->root)) + return; + + QModelIndex start; + if (currentIndex().isValid()) + start = currentIndex(); + else + start = d->model->index(0, 0, d->root); + + QTime now(QTime::currentTime()); + bool skipRow = false; + if (search.isEmpty() + || (d->keyboardInputTime.msecsTo(now) > QApplication::keyboardInputInterval())) { + d->keyboardInput = search; + skipRow = true; + } else { + d->keyboardInput += search; + } + d->keyboardInputTime = now; + + // special case for searches with same key like 'aaaaa' + bool sameKey = false; + if (d->keyboardInput.length() > 1) { + int c = d->keyboardInput.count(d->keyboardInput.at(d->keyboardInput.length() - 1)); + sameKey = (c == d->keyboardInput.length()); + if (sameKey) + skipRow = true; + } + + // skip if we are searching for the same key or a new search started + if (skipRow) { + if (indexBelow(start).isValid()) + start = indexBelow(start); + else + start = d->model->index(0, start.column(), d->root); + } + + d->executePostedLayout(); + int startIndex = d->viewIndex(start); + if (startIndex <= -1) + return; + + int previousLevel = -1; + int bestAbove = -1; + int bestBelow = -1; + QString searchString = sameKey ? QString(d->keyboardInput.at(0)) : d->keyboardInput; + for (int i = 0; i < d->viewItems.count(); ++i) { + if ((int)d->viewItems.at(i).level > previousLevel) { + QModelIndex searchFrom = d->viewItems.at(i).index; + if (searchFrom.parent() == start.parent()) + searchFrom = start; + QModelIndexList match = d->model->match(searchFrom, Qt::DisplayRole, searchString); + if (match.count()) { + int hitIndex = d->viewIndex(match.at(0)); + if (hitIndex >= 0 && hitIndex < startIndex) + bestAbove = bestAbove == -1 ? hitIndex : qMin(hitIndex, bestAbove); + else if (hitIndex >= startIndex) + bestBelow = bestBelow == -1 ? hitIndex : qMin(hitIndex, bestBelow); + } + } + previousLevel = d->viewItems.at(i).level; + } + + QModelIndex index; + if (bestBelow > -1) + index = d->viewItems.at(bestBelow).index; + else if (bestAbove > -1) + index = d->viewItems.at(bestAbove).index; + + if (index.isValid()) { + QItemSelectionModel::SelectionFlags flags = (d->selectionMode == SingleSelection + ? QItemSelectionModel::SelectionFlags( + QItemSelectionModel::ClearAndSelect + |d->selectionBehaviorFlags()) + : QItemSelectionModel::SelectionFlags( + QItemSelectionModel::NoUpdate)); + selectionModel()->setCurrentIndex(index, flags); + } +} + +/*! + Returns the rectangle on the viewport occupied by the item at \a index. + If the index is not visible or explicitly hidden, the returned rectangle is invalid. +*/ +QRect QTreeView::visualRect(const QModelIndex &index) const +{ + Q_D(const QTreeView); + + if (!d->isIndexValid(index) || isIndexHidden(index)) + return QRect(); + + d->executePostedLayout(); + + int vi = d->viewIndex(index); + if (vi < 0) + return QRect(); + + bool spanning = d->viewItems.at(vi).spanning; + + // if we have a spanning item, make the selection stretch from left to right + int x = (spanning ? 0 : columnViewportPosition(index.column())); + int w = (spanning ? d->header->length() : columnWidth(index.column())); + // handle indentation + if (index.column() == 0) { + int i = d->indentationForItem(vi); + w -= i; + if (!isRightToLeft()) + x += i; + } + + int y = d->coordinateForItem(vi); + int h = d->itemHeight(vi); + + return QRect(x, y, w, h); +} + +/*! + Scroll the contents of the tree view until the given model item + \a index is visible. The \a hint parameter specifies more + precisely where the item should be located after the + operation. + If any of the parents of the model item are collapsed, they will + be expanded to ensure that the model item is visible. +*/ +void QTreeView::scrollTo(const QModelIndex &index, ScrollHint hint) +{ + Q_D(QTreeView); + + if (!d->isIndexValid(index)) + return; + + d->executePostedLayout(); + d->updateScrollBars(); + + // Expand all parents if the parent(s) of the node are not expanded. + QModelIndex parent = index.parent(); + while (parent.isValid() && state() == NoState && d->itemsExpandable) { + if (!isExpanded(parent)) + expand(parent); + parent = d->model->parent(parent); + } + + int item = d->viewIndex(index); + if (item < 0) + return; + + QRect area = d->viewport->rect(); + + // vertical + if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) { + int top = verticalScrollBar()->value(); + int bottom = top + verticalScrollBar()->pageStep(); + if (hint == EnsureVisible && item >= top && item < bottom) { + // nothing to do + } else if (hint == PositionAtTop || (hint == EnsureVisible && item < top)) { + verticalScrollBar()->setValue(item); + } else { // PositionAtBottom or PositionAtCenter + int itemLocation = item; + int y = (hint == PositionAtCenter + ? area.height() / 2 + : area.height()); + while (y > 0 && item > 0) + y -= d->itemHeight(item--); + // end up half over the top of the area + if (y < 0 && item < itemLocation) + ++item; + // end up half over the bottom of the area + if (item >= 0 && item < itemLocation) + ++item; + verticalScrollBar()->setValue(item); + } + } else { // ScrollPerPixel + QRect rect(columnViewportPosition(index.column()), + d->coordinateForItem(item), // ### slow for items outside the view + columnWidth(index.column()), + d->itemHeight(item)); + + if (rect.isEmpty()) { + // nothing to do + } else if (hint == EnsureVisible && area.contains(rect)) { + d->setDirtyRegion(rect); + // nothing to do + } else { + bool above = (hint == EnsureVisible + && (rect.top() < area.top() + || area.height() < rect.height())); + bool below = (hint == EnsureVisible + && rect.bottom() > area.bottom() + && rect.height() < area.height()); + + int verticalValue = verticalScrollBar()->value(); + if (hint == PositionAtTop || above) + verticalValue += rect.top(); + else if (hint == PositionAtBottom || below) + verticalValue += rect.bottom() - area.height(); + else if (hint == PositionAtCenter) + verticalValue += rect.top() - ((area.height() - rect.height()) / 2); + verticalScrollBar()->setValue(verticalValue); + } + } + // horizontal + int viewportWidth = d->viewport->width(); + int horizontalOffset = d->header->offset(); + int horizontalPosition = d->header->sectionPosition(index.column()); + int cellWidth = d->header->sectionSize(index.column()); + + if (hint == PositionAtCenter) { + horizontalScrollBar()->setValue(horizontalPosition - ((viewportWidth - cellWidth) / 2)); + } else { + if (horizontalPosition - horizontalOffset < 0 || cellWidth > viewportWidth) + horizontalScrollBar()->setValue(horizontalPosition); + else if (horizontalPosition - horizontalOffset + cellWidth > viewportWidth) + horizontalScrollBar()->setValue(horizontalPosition - viewportWidth + cellWidth); + } +} + +/*! + \reimp +*/ +void QTreeView::timerEvent(QTimerEvent *event) +{ + Q_D(QTreeView); + if (event->timerId() == d->columnResizeTimerID) { + updateGeometries(); + killTimer(d->columnResizeTimerID); + d->columnResizeTimerID = 0; + QRect rect; + int viewportHeight = d->viewport->height(); + int viewportWidth = d->viewport->width(); + for (int i = d->columnsToUpdate.size() - 1; i >= 0; --i) { + int column = d->columnsToUpdate.at(i); + int x = columnViewportPosition(column); + if (isRightToLeft()) + rect |= QRect(0, 0, x + columnWidth(column), viewportHeight); + else + rect |= QRect(x, 0, viewportWidth - x, viewportHeight); + } + d->viewport->update(rect.normalized()); + d->columnsToUpdate.clear(); + } else if (event->timerId() == d->openTimer.timerId()) { + QPoint pos = d->viewport->mapFromGlobal(QCursor::pos()); + if (state() == QAbstractItemView::DraggingState + && d->viewport->rect().contains(pos)) { + QModelIndex index = indexAt(pos); + setExpanded(index, !isExpanded(index)); + } + d->openTimer.stop(); + } + + QAbstractItemView::timerEvent(event); +} + +/*! + \reimp +*/ +#ifndef QT_NO_DRAGANDDROP +void QTreeView::dragMoveEvent(QDragMoveEvent *event) +{ + Q_D(QTreeView); + if (d->autoExpandDelay >= 0) + d->openTimer.start(d->autoExpandDelay, this); + QAbstractItemView::dragMoveEvent(event); +} +#endif + +/*! + \reimp +*/ +bool QTreeView::viewportEvent(QEvent *event) +{ + Q_D(QTreeView); + switch (event->type()) { + case QEvent::HoverEnter: + case QEvent::HoverLeave: + case QEvent::HoverMove: { + QHoverEvent *he = static_cast<QHoverEvent*>(event); + int oldBranch = d->hoverBranch; + d->hoverBranch = d->itemDecorationAt(he->pos()); + if (oldBranch != d->hoverBranch) { + QRect oldRect = visualRect(d->modelIndex(oldBranch)); + QRect newRect = visualRect(d->modelIndex(d->hoverBranch)); + viewport()->update(oldRect.left() - d->indent, oldRect.top(), d->indent, oldRect.height()); + viewport()->update(newRect.left() - d->indent, newRect.top(), d->indent, newRect.height()); + } + if (selectionBehavior() == QAbstractItemView::SelectRows) { + QRect oldHoverRect = visualRect(d->hover); + QRect newHoverRect = visualRect(indexAt(he->pos())); + viewport()->update(QRect(0, newHoverRect.y(), viewport()->width(), newHoverRect.height())); + viewport()->update(QRect(0, oldHoverRect.y(), viewport()->width(), oldHoverRect.height())); + } + break; } + default: + break; + } + return QAbstractItemView::viewportEvent(event); +} + +/*! + \reimp +*/ +void QTreeView::paintEvent(QPaintEvent *event) +{ + Q_D(QTreeView); + d->executePostedLayout(); + QPainter painter(viewport()); + if (d->isAnimating()) { + drawTree(&painter, event->region() - d->animationRect()); + d->drawAnimatedOperation(&painter); + } else { + drawTree(&painter, event->region()); +#ifndef QT_NO_DRAGANDDROP + d->paintDropIndicator(&painter); +#endif + } +} + +void QTreeViewPrivate::paintAlternatingRowColors(QPainter *painter, QStyleOptionViewItemV4 *option, int y, int bottom) const +{ + Q_Q(const QTreeView); + if (!alternatingColors || !q->style()->styleHint(QStyle::SH_ItemView_PaintAlternatingRowColorsForEmptyArea, option, q)) + return; + int rowHeight = defaultItemHeight; + if (rowHeight <= 0) { + rowHeight = itemDelegate->sizeHint(*option, QModelIndex()).height(); + if (rowHeight <= 0) + return; + } + while (y <= bottom) { + option->rect.setRect(0, y, viewport->width(), rowHeight); + if (current & 1) { + option->features |= QStyleOptionViewItemV2::Alternate; + } else { + option->features &= ~QStyleOptionViewItemV2::Alternate; + } + ++current; + q->style()->drawPrimitive(QStyle::PE_PanelItemViewRow, option, painter, q); + y += rowHeight; + } +} + +bool QTreeViewPrivate::expandOrCollapseItemAtPos(const QPoint &pos) +{ + Q_Q(QTreeView); + // we want to handle mousePress in EditingState (persistent editors) + if ((q->state() != QAbstractItemView::NoState + && q->state() != QAbstractItemView::EditingState) + || !viewport->rect().contains(pos)) + return true; + + int i = itemDecorationAt(pos); + if ((i != -1) && q->itemsExpandable() && hasVisibleChildren(viewItems.at(i).index)) { + if (viewItems.at(i).expanded) + collapse(i, true); + else + expand(i, true); + if (!isAnimating()) { + q->updateGeometries(); + q->viewport()->update(); + } + return true; + } + return false; +} + +void QTreeViewPrivate::_q_modelDestroyed() +{ + //we need to clear that list because it contais QModelIndex to + //the model currently being destroyed + viewItems.clear(); + QAbstractItemViewPrivate::_q_modelDestroyed(); +} + +/*! + \since 4.2 + Draws the part of the tree intersecting the given \a region using the specified + \a painter. + + \sa paintEvent() +*/ +void QTreeView::drawTree(QPainter *painter, const QRegion ®ion) const +{ + Q_D(const QTreeView); + const QVector<QTreeViewItem> viewItems = d->viewItems; + + QStyleOptionViewItemV4 option = d->viewOptionsV4(); + const QStyle::State state = option.state; + d->current = 0; + + if (viewItems.count() == 0 || d->header->count() == 0 || !d->itemDelegate) { + d->paintAlternatingRowColors(painter, &option, 0, region.boundingRect().bottom()+1); + return; + } + + int firstVisibleItemOffset = 0; + const int firstVisibleItem = d->firstVisibleItem(&firstVisibleItemOffset); + if (firstVisibleItem < 0) { + d->paintAlternatingRowColors(painter, &option, 0, region.boundingRect().bottom()+1); + return; + } + + const int viewportWidth = d->viewport->width(); + + QVector<QRect> rects = region.rects(); + QVector<int> drawn; + bool multipleRects = (rects.size() > 1); + for (int a = 0; a < rects.size(); ++a) { + const QRect area = (multipleRects + ? QRect(0, rects.at(a).y(), viewportWidth, rects.at(a).height()) + : rects.at(a)); + d->leftAndRight = d->startAndEndColumns(area); + + int i = firstVisibleItem; // the first item at the top of the viewport + int y = firstVisibleItemOffset; // we may only see part of the first item + + // start at the top of the viewport and iterate down to the update area + for (; i < viewItems.count(); ++i) { + const int itemHeight = d->itemHeight(i); + if (y + itemHeight >= area.top()) + break; + y += itemHeight; + } + + // paint the visible rows + for (; i < viewItems.count() && y <= area.bottom(); ++i) { + const int itemHeight = d->itemHeight(i); + option.rect.setRect(0, y, viewportWidth, itemHeight); + option.state = state | (viewItems.at(i).expanded + ? QStyle::State_Open : QStyle::State_None); + d->current = i; + d->spanning = viewItems.at(i).spanning; + if (!multipleRects || !drawn.contains(i)) { + drawRow(painter, option, viewItems.at(i).index); + if (multipleRects) // even if the rect only intersects the item, + drawn.append(i); // the entire item will be painted + } + y += itemHeight; + } + + if (y <= area.bottom()) { + d->current = i; + d->paintAlternatingRowColors(painter, &option, y, area.bottom()); + } + } +} + +/// ### move to QObject :) +static inline bool ancestorOf(QObject *widget, QObject *other) +{ + for (QObject *parent = other; parent != 0; parent = parent->parent()) { + if (parent == widget) + return true; + } + return false; +} + +/*! + Draws the row in the tree view that contains the model item \a index, + using the \a painter given. The \a option control how the item is + displayed. + + \sa setAlternatingRowColors() +*/ +void QTreeView::drawRow(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + Q_D(const QTreeView); + QStyleOptionViewItemV4 opt = option; + const QPoint offset = d->scrollDelayOffset; + const int y = option.rect.y() + offset.y(); + const QModelIndex parent = index.parent(); + const QHeaderView *header = d->header; + const QModelIndex current = currentIndex(); + const QModelIndex hover = d->hover; + const bool reverse = isRightToLeft(); + const QStyle::State state = opt.state; + const bool spanning = d->spanning; + const int left = (spanning ? header->visualIndex(0) : d->leftAndRight.first); + const int right = (spanning ? header->visualIndex(0) : d->leftAndRight.second); + const bool alternate = d->alternatingColors; + const bool enabled = (state & QStyle::State_Enabled) != 0; + const bool allColumnsShowFocus = d->allColumnsShowFocus; + + + // when the row contains an index widget which has focus, + // we want to paint the entire row as active + bool indexWidgetHasFocus = false; + if ((current.row() == index.row()) && !d->editors.isEmpty()) { + const int r = index.row(); + QWidget *fw = QApplication::focusWidget(); + for (int c = 0; c < header->count(); ++c) { + QModelIndex idx = d->model->index(r, c, parent); + if (QWidget *editor = indexWidget(idx)) { + if (ancestorOf(editor, fw)) { + indexWidgetHasFocus = true; + break; + } + } + } + } + + const bool widgetHasFocus = hasFocus(); + bool currentRowHasFocus = false; + if (allColumnsShowFocus && widgetHasFocus && current.isValid()) { + // check if the focus index is before or after the visible columns + const int r = index.row(); + for (int c = 0; c < left && !currentRowHasFocus; ++c) { + QModelIndex idx = d->model->index(r, c, parent); + currentRowHasFocus = (idx == current); + } + QModelIndex parent = d->model->parent(index); + for (int c = right; c < header->count() && !currentRowHasFocus; ++c) { + currentRowHasFocus = (d->model->index(r, c, parent) == current); + } + } + + // ### special case: treeviews with multiple columns draw + // the selections differently than with only one column + opt.showDecorationSelected = (d->selectionBehavior & SelectRows) + || option.showDecorationSelected; + + int width, height = option.rect.height(); + int position; + QModelIndex modelIndex; + int columnCount = header->count(); + const bool hoverRow = selectionBehavior() == QAbstractItemView::SelectRows + && index.parent() == hover.parent() + && index.row() == hover.row(); + + /* 'left' and 'right' are the left-most and right-most visible visual indices. + Compute the first visible logical indices before and after the left and right. + We will use these values to determine the QStyleOptionViewItemV4::viewItemPosition. */ + int logicalIndexBeforeLeft = -1, logicalIndexAfterRight = -1; + for (int visualIndex = left - 1; visualIndex >= 0; --visualIndex) { + int logicalIndex = header->logicalIndex(visualIndex); + if (!header->isSectionHidden(logicalIndex)) { + logicalIndexBeforeLeft = logicalIndex; + break; + } + } + QVector<int> logicalIndices; // vector of currently visibly logical indices + for (int visualIndex = left; visualIndex < columnCount; ++visualIndex) { + int logicalIndex = header->logicalIndex(visualIndex); + if (!header->isSectionHidden(logicalIndex)) { + if (visualIndex > right) { + logicalIndexAfterRight = logicalIndex; + break; + } + logicalIndices.append(logicalIndex); + } + } + + for (int currentLogicalSection = 0; currentLogicalSection < logicalIndices.count(); ++currentLogicalSection) { + int headerSection = logicalIndices.at(currentLogicalSection); + position = columnViewportPosition(headerSection) + offset.x(); + width = header->sectionSize(headerSection); + + if (spanning) { + int lastSection = header->logicalIndex(header->count() - 1); + if (!reverse) { + width = columnViewportPosition(lastSection) + header->sectionSize(lastSection) - position; + } else { + width += position - columnViewportPosition(lastSection); + position = columnViewportPosition(lastSection); + } + } + + modelIndex = d->model->index(index.row(), headerSection, parent); + if (!modelIndex.isValid()) + continue; + opt.state = state; + + // determine the viewItemPosition depending on the position of column 0 + int nextLogicalSection = currentLogicalSection + 1 >= logicalIndices.count() + ? logicalIndexAfterRight + : logicalIndices.at(currentLogicalSection + 1); + int prevLogicalSection = currentLogicalSection - 1 < 0 + ? logicalIndexBeforeLeft + : logicalIndices.at(currentLogicalSection - 1); + if (columnCount == 1 || (nextLogicalSection == 0 && prevLogicalSection == -1) + || (headerSection == 0 && nextLogicalSection == -1)) + opt.viewItemPosition = QStyleOptionViewItemV4::OnlyOne; + else if (headerSection == 0 || (nextLogicalSection != 0 && prevLogicalSection == -1)) + opt.viewItemPosition = QStyleOptionViewItemV4::Beginning; + else if (nextLogicalSection == 0 || nextLogicalSection == -1) + opt.viewItemPosition = QStyleOptionViewItemV4::End; + else + opt.viewItemPosition = QStyleOptionViewItemV4::Middle; + + // fake activeness when row editor has focus + if (indexWidgetHasFocus) + opt.state |= QStyle::State_Active; + + if (d->selectionModel->isSelected(modelIndex)) + opt.state |= QStyle::State_Selected; + if (widgetHasFocus && (current == modelIndex)) { + if (allColumnsShowFocus) + currentRowHasFocus = true; + else + opt.state |= QStyle::State_HasFocus; + } + if ((hoverRow || modelIndex == hover) + && (option.showDecorationSelected || (d->hoverBranch == -1))) + opt.state |= QStyle::State_MouseOver; + else + opt.state &= ~QStyle::State_MouseOver; + + if (enabled) { + QPalette::ColorGroup cg; + if ((d->model->flags(modelIndex) & Qt::ItemIsEnabled) == 0) { + opt.state &= ~QStyle::State_Enabled; + cg = QPalette::Disabled; + } else if (opt.state & QStyle::State_Active) { + cg = QPalette::Active; + } else { + cg = QPalette::Inactive; + } + opt.palette.setCurrentColorGroup(cg); + } + + if (alternate) { + if (d->current & 1) { + opt.features |= QStyleOptionViewItemV2::Alternate; + } else { + opt.features &= ~QStyleOptionViewItemV2::Alternate; + } + } + + /* Prior to Qt 4.3, the background of the branch (in selected state and + alternate row color was provided by the view. For backward compatibility, + this is now delegated to the style using PE_PanelViewItemRow which + does the appropriate fill */ + if (headerSection == 0) { + const int i = d->indentationForItem(d->current); + QRect branches(reverse ? position + width - i : position, y, i, height); + const bool setClipRect = branches.width() > width; + if (setClipRect) { + painter->save(); + painter->setClipRect(QRect(position, y, width, height)); + } + // draw background for the branch (selection + alternate row) + opt.rect = branches; + style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this); + + // draw background of the item (only alternate row). rest of the background + // is provided by the delegate + QStyle::State oldState = opt.state; + opt.state &= ~QStyle::State_Selected; + opt.rect.setRect(reverse ? position : i + position, y, width - i, height); + style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this); + opt.state = oldState; + + drawBranches(painter, branches, index); + if (setClipRect) + painter->restore(); + } else { + QStyle::State oldState = opt.state; + opt.state &= ~QStyle::State_Selected; + opt.rect.setRect(position, y, width, height); + style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this); + opt.state = oldState; + } + + if (const QWidget *widget = d->editorForIndex(modelIndex).editor) { + painter->save(); + painter->setClipRect(widget->geometry()); + d->delegateForIndex(modelIndex)->paint(painter, opt, modelIndex); + painter->restore(); + } else { + d->delegateForIndex(modelIndex)->paint(painter, opt, modelIndex); + } + } + + if (currentRowHasFocus) { + QStyleOptionFocusRect o; + o.QStyleOption::operator=(option); + o.state |= QStyle::State_KeyboardFocusChange; + QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) + ? QPalette::Normal : QPalette::Disabled; + o.backgroundColor = option.palette.color(cg, d->selectionModel->isSelected(index) + ? QPalette::Highlight : QPalette::Background); + int x = 0; + if (!option.showDecorationSelected) + x = header->sectionPosition(0) + d->indentationForItem(d->current); + QRect focusRect(x - header->offset(), y, header->length() - x, height); + o.rect = style()->visualRect(layoutDirection(), d->viewport->rect(), focusRect); + style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter); + // if we show focus on all columns and the first section is moved, + // we have to split the focus rect into two rects + if (allColumnsShowFocus && !option.showDecorationSelected + && header->sectionsMoved() && (header->visualIndex(0) != 0)) { + QRect sectionRect(0, y, header->sectionPosition(0), height); + o.rect = style()->visualRect(layoutDirection(), d->viewport->rect(), sectionRect); + style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter); + } + } +} + +/*! + Draws the branches in the tree view on the same row as the model item + \a index, using the \a painter given. The branches are drawn in the + rectangle specified by \a rect. +*/ +void QTreeView::drawBranches(QPainter *painter, const QRect &rect, + const QModelIndex &index) const +{ + Q_D(const QTreeView); + const bool reverse = isRightToLeft(); + const int indent = d->indent; + const int outer = d->rootDecoration ? 0 : 1; + const int item = d->current; + const QTreeViewItem &viewItem = d->viewItems.at(item); + int level = viewItem.level; + QRect primitive(reverse ? rect.left() : rect.right() + 1, rect.top(), indent, rect.height()); + + QModelIndex parent = index.parent(); + QModelIndex current = parent; + QModelIndex ancestor = current.parent(); + + QStyleOptionViewItemV2 opt = viewOptions(); + QStyle::State extraFlags = QStyle::State_None; + if (isEnabled()) + extraFlags |= QStyle::State_Enabled; + if (window()->isActiveWindow()) + extraFlags |= QStyle::State_Active; + QPoint oldBO = painter->brushOrigin(); + if (verticalScrollMode() == QAbstractItemView::ScrollPerPixel) + painter->setBrushOrigin(QPoint(0, verticalOffset())); + + if (d->alternatingColors) { + if (d->current & 1) { + opt.features |= QStyleOptionViewItemV2::Alternate; + } else { + opt.features &= ~QStyleOptionViewItemV2::Alternate; + } + } + + // When hovering over a row, pass State_Hover for painting the branch + // indicators if it has the decoration (aka branch) selected. + bool hoverRow = selectionBehavior() == QAbstractItemView::SelectRows + && opt.showDecorationSelected + && index.parent() == d->hover.parent() + && index.row() == d->hover.row(); + + if (d->selectionModel->isSelected(index)) + extraFlags |= QStyle::State_Selected; + + if (level >= outer) { + // start with the innermost branch + primitive.moveLeft(reverse ? primitive.left() : primitive.left() - indent); + opt.rect = primitive; + + const bool expanded = viewItem.expanded; + const bool children = (((expanded && viewItem.total > 0)) // already laid out and has children + || d->hasVisibleChildren(index)); // not laid out yet, so we don't know + bool moreSiblings = false; + if (d->hiddenIndexes.isEmpty()) + moreSiblings = (d->model->rowCount(parent) - 1 > index.row()); + else + moreSiblings = ((d->viewItems.size() > item +1) + && (d->viewItems.at(item + 1).index.parent() == parent)); + + opt.state = QStyle::State_Item | extraFlags + | (moreSiblings ? QStyle::State_Sibling : QStyle::State_None) + | (children ? QStyle::State_Children : QStyle::State_None) + | (expanded ? QStyle::State_Open : QStyle::State_None); + if (hoverRow || item == d->hoverBranch) + opt.state |= QStyle::State_MouseOver; + else + opt.state &= ~QStyle::State_MouseOver; + style()->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this); + } + // then go out level by level + for (--level; level >= outer; --level) { // we have already drawn the innermost branch + primitive.moveLeft(reverse ? primitive.left() + indent : primitive.left() - indent); + opt.rect = primitive; + opt.state = extraFlags; + bool moreSiblings = false; + if (d->hiddenIndexes.isEmpty()) { + moreSiblings = (d->model->rowCount(ancestor) - 1 > current.row()); + } else { + int successor = item + viewItem.total + 1; + while (successor < d->viewItems.size() + && d->viewItems.at(successor).level >= uint(level)) { + const QTreeViewItem &successorItem = d->viewItems.at(successor); + if (successorItem.level == uint(level)) { + moreSiblings = true; + break; + } + successor += successorItem.total + 1; + } + } + if (moreSiblings) + opt.state |= QStyle::State_Sibling; + if (hoverRow || item == d->hoverBranch) + opt.state |= QStyle::State_MouseOver; + else + opt.state &= ~QStyle::State_MouseOver; + style()->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this); + current = ancestor; + ancestor = current.parent(); + } + painter->setBrushOrigin(oldBO); +} + +/*! + \reimp +*/ +void QTreeView::mousePressEvent(QMouseEvent *event) +{ + Q_D(QTreeView); + bool handled = false; + if (style()->styleHint(QStyle::SH_Q3ListViewExpand_SelectMouseType, 0, this) == QEvent::MouseButtonPress) + handled = d->expandOrCollapseItemAtPos(event->pos()); + if (!handled && d->itemDecorationAt(event->pos()) == -1) + QAbstractItemView::mousePressEvent(event); +} + +/*! + \reimp +*/ +void QTreeView::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(QTreeView); + if (d->itemDecorationAt(event->pos()) == -1) { + QAbstractItemView::mouseReleaseEvent(event); + } else { + if (state() == QAbstractItemView::DragSelectingState) + setState(QAbstractItemView::NoState); + if (style()->styleHint(QStyle::SH_Q3ListViewExpand_SelectMouseType, 0, this) == QEvent::MouseButtonRelease) + d->expandOrCollapseItemAtPos(event->pos()); + } +} + +/*! + \reimp +*/ +void QTreeView::mouseDoubleClickEvent(QMouseEvent *event) +{ + Q_D(QTreeView); + if (state() != NoState || !d->viewport->rect().contains(event->pos())) + return; + + int i = d->itemDecorationAt(event->pos()); + if (i == -1) { + i = d->itemAtCoordinate(event->y()); + if (i == -1) + return; // user clicked outside the items + + const QModelIndex &index = d->viewItems.at(i).index; + + int column = d->header->logicalIndexAt(event->x()); + QPersistentModelIndex persistent = index.sibling(index.row(), column); + + if (d->pressedIndex != persistent) { + mousePressEvent(event); + return; + } + + // signal handlers may change the model + emit doubleClicked(persistent); + + if (!persistent.isValid()) + return; + + if (edit(persistent, DoubleClicked, event) || state() != NoState) + return; // the double click triggered editing + + if (!style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, this)) + emit activated(persistent); + + d->executePostedLayout(); // we need to make sure viewItems is updated + if (d->itemsExpandable + && d->expandsOnDoubleClick + && d->hasVisibleChildren(persistent)) { + if (!((i < d->viewItems.count()) && (d->viewItems.at(i).index == persistent))) { + // find the new index of the item + for (i = 0; i < d->viewItems.count(); ++i) { + if (d->viewItems.at(i).index == persistent) + break; + } + if (i == d->viewItems.count()) + return; + } + if (d->viewItems.at(i).expanded) + d->collapse(i, true); + else + d->expand(i, true); + updateGeometries(); + viewport()->update(); + } + } +} + +/*! + \reimp +*/ +void QTreeView::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QTreeView); + if (d->itemDecorationAt(event->pos()) == -1) // ### what about expanding/collapsing state ? + QAbstractItemView::mouseMoveEvent(event); +} + +/*! + \reimp +*/ +void QTreeView::keyPressEvent(QKeyEvent *event) +{ + Q_D(QTreeView); + QModelIndex current = currentIndex(); + //this is the management of the expansion + if (d->isIndexValid(current) && d->model && d->itemsExpandable) { + switch (event->key()) { + case Qt::Key_Asterisk: { + QStack<QModelIndex> parents; + parents.push(current); + while (!parents.isEmpty()) { + QModelIndex parent = parents.pop(); + for (int row = 0; row < d->model->rowCount(parent); ++row) { + QModelIndex child = d->model->index(row, 0, parent); + if (!d->isIndexValid(child)) + break; + parents.push(child); + expand(child); + } + } + expand(current); + break; } + case Qt::Key_Plus: + expand(current); + break; + case Qt::Key_Minus: + collapse(current); + break; + } + } + + QAbstractItemView::keyPressEvent(event); +} + +/*! + \reimp +*/ +QModelIndex QTreeView::indexAt(const QPoint &point) const +{ + Q_D(const QTreeView); + d->executePostedLayout(); + + int visualIndex = d->itemAtCoordinate(point.y()); + QModelIndex idx = d->modelIndex(visualIndex); + if (!idx.isValid()) + return QModelIndex(); + + if (d->viewItems.at(visualIndex).spanning) + return idx; + + int column = d->columnAt(point.x()); + if (column == idx.column()) + return idx; + if (column < 0) + return QModelIndex(); + return idx.sibling(idx.row(), column); +} + +/*! + Returns the model index of the item above \a index. +*/ +QModelIndex QTreeView::indexAbove(const QModelIndex &index) const +{ + Q_D(const QTreeView); + if (!d->isIndexValid(index)) + return QModelIndex(); + d->executePostedLayout(); + int i = d->viewIndex(index); + if (--i < 0) + return QModelIndex(); + return d->viewItems.at(i).index; +} + +/*! + Returns the model index of the item below \a index. +*/ +QModelIndex QTreeView::indexBelow(const QModelIndex &index) const +{ + Q_D(const QTreeView); + if (!d->isIndexValid(index)) + return QModelIndex(); + d->executePostedLayout(); + int i = d->viewIndex(index); + if (++i >= d->viewItems.count()) + return QModelIndex(); + return d->viewItems.at(i).index; +} + +/*! + \internal + + Lays out the items in the tree view. +*/ +void QTreeView::doItemsLayout() +{ + Q_D(QTreeView); + d->viewItems.clear(); // prepare for new layout + QModelIndex parent = d->root; + if (d->model->hasChildren(parent)) { + d->layout(-1); + } + QAbstractItemView::doItemsLayout(); + d->header->doItemsLayout(); +} + +/*! + \reimp +*/ +void QTreeView::reset() +{ + Q_D(QTreeView); + d->expandedIndexes.clear(); + d->hiddenIndexes.clear(); + d->spanningIndexes.clear(); + d->viewItems.clear(); + QAbstractItemView::reset(); +} + +/*! + Returns the horizontal offset of the items in the treeview. + + Note that the tree view uses the horizontal header section + positions to determine the positions of columns in the view. + + \sa verticalOffset() +*/ +int QTreeView::horizontalOffset() const +{ + Q_D(const QTreeView); + return d->header->offset(); +} + +/*! + Returns the vertical offset of the items in the tree view. + + \sa horizontalOffset() +*/ +int QTreeView::verticalOffset() const +{ + Q_D(const QTreeView); + if (d->verticalScrollMode == QAbstractItemView::ScrollPerItem) { + if (d->uniformRowHeights) + return verticalScrollBar()->value() * d->defaultItemHeight; + // If we are scrolling per item and have non-uniform row heights, + // finding the vertical offset in pixels is going to be relatively slow. + // ### find a faster way to do this + int offset = 0; + for (int i = 0; i < d->viewItems.count(); ++i) { + if (i == verticalScrollBar()->value()) + return offset; + offset += d->itemHeight(i); + } + return 0; + } + // scroll per pixel + return verticalScrollBar()->value(); +} + +/*! + Move the cursor in the way described by \a cursorAction, using the + information provided by the button \a modifiers. +*/ +QModelIndex QTreeView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) +{ + Q_D(QTreeView); + Q_UNUSED(modifiers); + + d->executePostedLayout(); + + QModelIndex current = currentIndex(); + if (!current.isValid()) { + int i = d->below(-1); + int c = 0; + while (c < d->header->count() && d->header->isSectionHidden(c)) + ++c; + if (i < d->viewItems.count() && c < d->header->count()) { + return d->modelIndex(i, c); + } + return QModelIndex(); + } + int vi = -1; +#if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) + // Selection behavior is slightly different on the Mac. + if (d->selectionMode == QAbstractItemView::ExtendedSelection + && d->selectionModel + && d->selectionModel->hasSelection()) { + + const bool moveUpDown = (cursorAction == MoveUp || cursorAction == MoveDown); + const bool moveNextPrev = (cursorAction == MoveNext || cursorAction == MovePrevious); + const bool contiguousSelection = moveUpDown && (modifiers & Qt::ShiftModifier); + + // Use the outermost index in the selection as the current index + if (!contiguousSelection && (moveUpDown || moveNextPrev)) { + + // Find outermost index. + const bool useTopIndex = (cursorAction == MoveUp || cursorAction == MovePrevious); + int index = useTopIndex ? INT_MAX : INT_MIN; + const QItemSelection selection = d->selectionModel->selection(); + foreach (const QItemSelectionRange &range, selection) { + int candidate = d->viewIndex(useTopIndex ? range.topLeft() : range.bottomRight()); + if (candidate >= 0) + index = useTopIndex ? qMin(index, candidate) : qMax(index, candidate); + } + + if (index >= 0 && index < INT_MAX) + vi = index; + } + } +#endif + if (vi < 0) + vi = qMax(0, d->viewIndex(current)); + + switch (cursorAction) { + case MoveNext: + case MoveDown: +#ifdef QT_KEYPAD_NAVIGATION + if (vi == d->viewItems.count()-1 && QApplication::keypadNavigationEnabled()) + return d->model->index(0, current.column(), d->root); +#endif + return d->modelIndex(d->below(vi), current.column()); + case MovePrevious: + case MoveUp: +#ifdef QT_KEYPAD_NAVIGATION + if (vi == 0 && QApplication::keypadNavigationEnabled()) + return d->modelIndex(d->viewItems.count() - 1, current.column()); +#endif + return d->modelIndex(d->above(vi), current.column()); + case MoveLeft: { + QScrollBar *sb = horizontalScrollBar(); + if (vi < d->viewItems.count() && d->viewItems.at(vi).expanded && d->itemsExpandable && sb->value() == sb->minimum()) + d->collapse(vi, true); + else { + bool descend = style()->styleHint(QStyle::SH_ItemView_ArrowKeysNavigateIntoChildren, 0, this); + if (descend) { + QModelIndex par = current.parent(); + if (par.isValid() && par != rootIndex()) + return par; + else + descend = false; + } + if (!descend) { + if (d->selectionBehavior == SelectItems || d->selectionBehavior == SelectColumns) { + int visualColumn = d->header->visualIndex(current.column()) - 1; + while (visualColumn >= 0 && isColumnHidden(d->header->logicalIndex(visualColumn))) + visualColumn--; + int newColumn = d->header->logicalIndex(visualColumn); + QModelIndex next = current.sibling(current.row(), newColumn); + if (next.isValid()) + return next; + } + + sb->setValue(sb->value() - sb->singleStep()); + } + + } + updateGeometries(); + viewport()->update(); + break; + } + case MoveRight: + if (vi < d->viewItems.count() && !d->viewItems.at(vi).expanded && d->itemsExpandable + && d->hasVisibleChildren(d->viewItems.at(vi).index)) { + d->expand(vi, true); + } else { + bool descend = style()->styleHint(QStyle::SH_ItemView_ArrowKeysNavigateIntoChildren, 0, this); + if (descend) { + QModelIndex idx = d->modelIndex(d->below(vi)); + if (idx.parent() == current) + return idx; + else + descend = false; + } + if (!descend) { + if (d->selectionBehavior == SelectItems || d->selectionBehavior == SelectColumns) { + int visualColumn = d->header->visualIndex(current.column()) + 1; + while (visualColumn < d->model->columnCount(current.parent()) && isColumnHidden(d->header->logicalIndex(visualColumn))) + visualColumn++; + + QModelIndex next = current.sibling(current.row(), visualColumn); + if (next.isValid()) + return next; + } + + //last restort: we change the scrollbar value + QScrollBar *sb = horizontalScrollBar(); + sb->setValue(sb->value() + sb->singleStep()); + } + } + updateGeometries(); + viewport()->update(); + break; + case MovePageUp: + return d->modelIndex(d->pageUp(vi), current.column()); + case MovePageDown: + return d->modelIndex(d->pageDown(vi), current.column()); + case MoveHome: + return d->model->index(0, current.column(), d->root); + case MoveEnd: + return d->modelIndex(d->viewItems.count() - 1, current.column()); + } + return current; +} + +/*! + Applies the selection \a command to the items in or touched by the + rectangle, \a rect. + + \sa selectionCommand() +*/ +void QTreeView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command) +{ + Q_D(QTreeView); + if (!selectionModel() || rect.isNull()) + return; + + d->executePostedLayout(); + QPoint tl(isRightToLeft() ? qMax(rect.left(), rect.right()) + : qMin(rect.left(), rect.right()), qMin(rect.top(), rect.bottom())); + QPoint br(isRightToLeft() ? qMin(rect.left(), rect.right()) : + qMax(rect.left(), rect.right()), qMax(rect.top(), rect.bottom())); + QModelIndex topLeft = indexAt(tl); + QModelIndex bottomRight = indexAt(br); + if (!topLeft.isValid() && !bottomRight.isValid()) { + if (command & QItemSelectionModel::Clear) + selectionModel()->clear(); + return; + } + if (!topLeft.isValid() && !d->viewItems.isEmpty()) + topLeft = d->viewItems.first().index; + if (!bottomRight.isValid() && !d->viewItems.isEmpty()) { + const int column = d->header->logicalIndex(d->header->count() - 1); + const QModelIndex index = d->viewItems.last().index; + bottomRight = index.sibling(index.row(), column); + } + + if (!d->isIndexEnabled(topLeft) || !d->isIndexEnabled(bottomRight)) + return; + + d->select(topLeft, bottomRight, command); +} + +/*! + Returns the rectangle from the viewport of the items in the given + \a selection. +*/ +QRegion QTreeView::visualRegionForSelection(const QItemSelection &selection) const +{ + Q_D(const QTreeView); + if (selection.isEmpty()) + return QRegion(); + + QRegion selectionRegion; + for (int i = 0; i < selection.count(); ++i) { + QItemSelectionRange range = selection.at(i); + if (!range.isValid()) + continue; + QModelIndex parent = range.parent(); + QModelIndex leftIndex = range.topLeft(); + int columnCount = d->model->columnCount(parent); + while (leftIndex.isValid() && isIndexHidden(leftIndex)) { + if (leftIndex.column() + 1 < columnCount) + leftIndex = d->model->index(leftIndex.row(), leftIndex.column() + 1, parent); + else + leftIndex = QModelIndex(); + } + if (!leftIndex.isValid()) + continue; + const QRect leftRect = visualRect(leftIndex); + int top = leftRect.top(); + QModelIndex rightIndex = range.bottomRight(); + while (rightIndex.isValid() && isIndexHidden(rightIndex)) { + if (rightIndex.column() - 1 >= 0) + rightIndex = d->model->index(rightIndex.row(), rightIndex.column() - 1, parent); + else + rightIndex = QModelIndex(); + } + if (!rightIndex.isValid()) + continue; + const QRect rightRect = visualRect(rightIndex); + int bottom = rightRect.bottom(); + if (top > bottom) + qSwap<int>(top, bottom); + int height = bottom - top + 1; + if (d->header->sectionsMoved()) { + for (int c = range.left(); c <= range.right(); ++c) + selectionRegion += QRegion(QRect(columnViewportPosition(c), top, + columnWidth(c), height)); + } else { + QRect combined = leftRect|rightRect; + combined.setX(columnViewportPosition(isRightToLeft() ? range.right() : range.left())); + selectionRegion += combined; + } + } + return selectionRegion; +} + +/*! + \reimp +*/ +QModelIndexList QTreeView::selectedIndexes() const +{ + QModelIndexList viewSelected; + QModelIndexList modelSelected; + if (selectionModel()) + modelSelected = selectionModel()->selectedIndexes(); + for (int i = 0; i < modelSelected.count(); ++i) { + // check that neither the parents nor the index is hidden before we add + QModelIndex index = modelSelected.at(i); + while (index.isValid() && !isIndexHidden(index)) + index = index.parent(); + if (index.isValid()) + continue; + viewSelected.append(modelSelected.at(i)); + } + return viewSelected; +} + +/*! + Scrolls the contents of the tree view by (\a dx, \a dy). +*/ +void QTreeView::scrollContentsBy(int dx, int dy) +{ + Q_D(QTreeView); + + d->delayedAutoScroll.stop(); // auto scroll was canceled by the user scrolling + + dx = isRightToLeft() ? -dx : dx; + if (dx) { + if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) { + int oldOffset = d->header->offset(); + if (horizontalScrollBar()->value() == horizontalScrollBar()->maximum()) + d->header->setOffsetToLastSection(); + else + d->header->setOffsetToSectionPosition(horizontalScrollBar()->value()); + int newOffset = d->header->offset(); + dx = isRightToLeft() ? newOffset - oldOffset : oldOffset - newOffset; + } else { + d->header->setOffset(horizontalScrollBar()->value()); + } + } + + if (d->viewItems.isEmpty() || d->defaultItemHeight == 0) + return; + + // guestimate the number of items in the viewport + int viewCount = d->viewport->height() / d->defaultItemHeight; + int maxDeltaY = qMin(d->viewItems.count(), viewCount); + // no need to do a lot of work if we are going to redraw the whole thing anyway + if (qAbs(dy) > qAbs(maxDeltaY) && d->editors.isEmpty()) { + verticalScrollBar()->update(); + d->viewport->update(); + return; + } + + if (dy && verticalScrollMode() == QAbstractItemView::ScrollPerItem) { + int currentScrollbarValue = verticalScrollBar()->value(); + int previousScrollbarValue = currentScrollbarValue + dy; // -(-dy) + int currentViewIndex = currentScrollbarValue; // the first visible item + int previousViewIndex = previousScrollbarValue; + const QVector<QTreeViewItem> viewItems = d->viewItems; + dy = 0; + if (previousViewIndex < currentViewIndex) { // scrolling down + for (int i = previousViewIndex; i < currentViewIndex; ++i) { + if (i < d->viewItems.count()) + dy -= d->itemHeight(i); + } + } else if (previousViewIndex > currentViewIndex) { // scrolling up + for (int i = previousViewIndex - 1; i >= currentViewIndex; --i) { + if (i < d->viewItems.count()) + dy += d->itemHeight(i); + } + } + } + + d->scrollContentsBy(dx, dy); +} + +/*! + This slot is called whenever a column has been moved. +*/ +void QTreeView::columnMoved() +{ + Q_D(QTreeView); + updateEditorGeometries(); + d->viewport->update(); +} + +/*! + \internal +*/ +void QTreeView::reexpand() +{ + // do nothing +} + +/*! + \internal +*/ +static bool treeViewItemLessThan(const QTreeViewItem &left, + const QTreeViewItem &right) +{ + if (left.level != right.level) { + Q_ASSERT(left.level > right.level); + QModelIndex leftParent = left.index.parent(); + QModelIndex rightParent = right.index.parent(); + // computer parent, don't get + while (leftParent.isValid() && leftParent.parent() != rightParent) + leftParent = leftParent.parent(); + return (leftParent.row() < right.index.row()); + } + return (left.index.row() < right.index.row()); +} + +/*! + Informs the view that the rows from the \a start row to the \a end row + inclusive have been inserted into the \a parent model item. +*/ +void QTreeView::rowsInserted(const QModelIndex &parent, int start, int end) +{ + Q_D(QTreeView); + // if we are going to do a complete relayout anyway, there is no need to update + if (d->delayedPendingLayout) { + QAbstractItemView::rowsInserted(parent, start, end); + return; + } + + //don't add a hierarchy on a column != 0 + if (parent.column() != 0 && parent.isValid()) { + QAbstractItemView::rowsInserted(parent, start, end); + return; + } + + if (parent != d->root && !d->isIndexExpanded(parent) && d->model->rowCount(parent) > (end - start) + 1) { + QAbstractItemView::rowsInserted(parent, start, end); + return; + } + + const int parentItem = d->viewIndex(parent); + if (((parentItem != -1) && d->viewItems.at(parentItem).expanded && updatesEnabled()) + || (parent == d->root)) { + const uint childLevel = (parentItem == -1) + ? uint(0) : d->viewItems.at(parentItem).level + 1; + const int firstChildItem = parentItem + 1; + const int lastChildItem = firstChildItem + ((parentItem == -1) + ? d->viewItems.count() + : d->viewItems.at(parentItem).total) - 1; + + int firstColumn = 0; + while (isColumnHidden(firstColumn) && firstColumn < header()->count() - 1) + ++firstColumn; + + const int delta = end - start + 1; + QVector<QTreeViewItem> insertedItems(delta); + for (int i = 0; i < delta; ++i) { + insertedItems[i].index = d->model->index(i + start, firstColumn, parent); + insertedItems[i].level = childLevel; + } + if (d->viewItems.isEmpty()) + d->defaultItemHeight = indexRowSizeHint(insertedItems[0].index); + + int insertPos; + if (lastChildItem < firstChildItem) { // no children + insertPos = firstChildItem; + } else { + // do a binary search to figure out where to insert + QVector<QTreeViewItem>::iterator it; + it = qLowerBound(d->viewItems.begin() + firstChildItem, + d->viewItems.begin() + lastChildItem + 1, + insertedItems.at(0), treeViewItemLessThan); + insertPos = it - d->viewItems.begin(); + + // update stale model indexes of siblings + for (int item = insertPos; item <= lastChildItem; ) { + Q_ASSERT(d->viewItems.at(item).level == childLevel); + const QModelIndex modelIndex = d->viewItems.at(item).index; + //Q_ASSERT(modelIndex.parent() == parent); + d->viewItems[item].index = d->model->index( + modelIndex.row() + delta, modelIndex.column(), parent); + + if (!d->viewItems[item].index.isValid()) { + // Something really bad is happening, a bad model is + // often the cause. We can't optimize in this case :( + qWarning() << "QTreeView::rowsInserted internal representation of the model has been corrupted, resetting."; + doItemsLayout(); + return; + } + + item += d->viewItems.at(item).total + 1; + } + } + + d->viewItems.insert(insertPos, delta, insertedItems.at(0)); + if (delta > 1) { + qCopy(insertedItems.begin() + 1, insertedItems.end(), + d->viewItems.begin() + insertPos + 1); + } + + d->updateChildCount(parentItem, delta); + updateGeometries(); + viewport()->update(); + } else if ((parentItem != -1) && d->viewItems.at(parentItem).expanded) { + d->doDelayedItemsLayout(); + } else if (parentItem != -1 && (d->model->rowCount(parent) == end - start + 1)) { + // the parent just went from 0 children to having some update to re-paint the decoration + viewport()->update(); + } + QAbstractItemView::rowsInserted(parent, start, end); +} + +/*! + Informs the view that the rows from the \a start row to the \a end row + inclusive are about to removed from the given \a parent model item. +*/ +void QTreeView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + Q_D(QTreeView); + d->rowsRemoved(parent, start, end, false); + QAbstractItemView::rowsAboutToBeRemoved(parent, start, end); +} + +/*! + \since 4.1 + + Informs the view that the rows from the \a start row to the \a end row + inclusive have been removed from the given \a parent model item. +*/ +void QTreeView::rowsRemoved(const QModelIndex &parent, int start, int end) +{ + Q_D(QTreeView); + d->rowsRemoved(parent, start, end, true); +} + +/*! + Informs the tree view that the number of columns in the tree view has + changed from \a oldCount to \a newCount. +*/ +void QTreeView::columnCountChanged(int oldCount, int newCount) +{ + Q_D(QTreeView); + if (oldCount == 0 && newCount > 0) { + //if the first column has just been added we need to relayout. + d->doDelayedItemsLayout(); + } + + if (isVisible()) + updateGeometries(); + viewport()->update(); +} + +/*! + Resizes the \a column given to the size of its contents. + + \sa columnWidth(), setColumnWidth() +*/ +void QTreeView::resizeColumnToContents(int column) +{ + Q_D(QTreeView); + d->executePostedLayout(); + if (column < 0 || column >= d->header->count()) + return; + int contents = sizeHintForColumn(column); + int header = d->header->isHidden() ? 0 : d->header->sectionSizeHint(column); + d->header->resizeSection(column, qMax(contents, header)); +} + +/*! + \obsolete + \overload + + Sorts the model by the values in the given \a column. +*/ +void QTreeView::sortByColumn(int column) +{ + Q_D(QTreeView); + sortByColumn(column, d->header->sortIndicatorOrder()); +} + +/*! + \since 4.2 + + Sets the model up for sorting by the values in the given \a column and \a order. + + \a column may be -1, in which case no sort indicator will be shown + and the model will return to its natural, unsorted order. Note that not + all models support this and may even crash in this case. + + \sa sortingEnabled +*/ +void QTreeView::sortByColumn(int column, Qt::SortOrder order) +{ + Q_D(QTreeView); + + //will emit a signal connected to _q_sortIndicatorChanged, which then actually sorts + d->header->setSortIndicator(column, order); +} + +/*! + \reimp +*/ +void QTreeView::selectAll() +{ + Q_D(QTreeView); + if (!selectionModel()) + return; + SelectionMode mode = d->selectionMode; + d->executePostedLayout(); //make sure we lay out the items + if (mode != SingleSelection && !d->viewItems.isEmpty()) + d->select(d->viewItems.first().index, d->viewItems.last().index, + QItemSelectionModel::ClearAndSelect + |QItemSelectionModel::Rows); +} + +/*! + \since 4.2 + Expands all expandable items. + + Warning: if the model contains a large number of items, + this function will take some time to execute. + + \sa collapseAll() expand() collapse() setExpanded() +*/ +void QTreeView::expandAll() +{ + Q_D(QTreeView); + d->viewItems.clear(); + d->expandedIndexes.clear(); + d->interruptDelayedItemsLayout(); + d->layout(-1); + for (int i = 0; i < d->viewItems.count(); ++i) { + if (d->viewItems[i].expanded) + continue; + d->viewItems[i].expanded = true; + d->layout(i); + QModelIndex idx = d->viewItems.at(i).index; + d->expandedIndexes.insert(idx.sibling(idx.row(), 0)); + } + updateGeometries(); + d->viewport->update(); +} + +/*! + \since 4.2 + + Collapses all expanded items. + + \sa expandAll() expand() collapse() setExpanded() +*/ +void QTreeView::collapseAll() +{ + Q_D(QTreeView); + d->expandedIndexes.clear(); + doItemsLayout(); +} + +/*! + \since 4.3 + Expands all expandable items to the given \a depth. + + \sa expandAll() collapseAll() expand() collapse() setExpanded() +*/ +void QTreeView::expandToDepth(int depth) +{ + Q_D(QTreeView); + d->viewItems.clear(); + d->expandedIndexes.clear(); + d->interruptDelayedItemsLayout(); + d->layout(-1); + for (int i = 0; i < d->viewItems.count(); ++i) { + if (d->viewItems.at(i).level <= (uint)depth) { + d->viewItems[i].expanded = true; + d->layout(i); + d->storeExpanded(d->viewItems.at(i).index); + } + } + updateGeometries(); + d->viewport->update(); +} + +/*! + This function is called whenever \a{column}'s size is changed in + the header. \a oldSize and \a newSize give the previous size and + the new size in pixels. + + \sa setColumnWidth() +*/ +void QTreeView::columnResized(int column, int /* oldSize */, int /* newSize */) +{ + Q_D(QTreeView); + d->columnsToUpdate.append(column); + if (d->columnResizeTimerID == 0) + d->columnResizeTimerID = startTimer(0); +} + +/*! + \reimp +*/ +void QTreeView::updateGeometries() +{ + Q_D(QTreeView); + if (d->header) { + if (d->geometryRecursionBlock) + return; + d->geometryRecursionBlock = true; + QSize hint = d->header->isHidden() ? QSize(0, 0) : d->header->sizeHint(); + setViewportMargins(0, hint.height(), 0, 0); + QRect vg = d->viewport->geometry(); + QRect geometryRect(vg.left(), vg.top() - hint.height(), vg.width(), hint.height()); + d->header->setGeometry(geometryRect); + //d->header->setOffset(horizontalScrollBar()->value()); // ### bug ??? + QMetaObject::invokeMethod(d->header, "updateGeometries"); + d->updateScrollBars(); + d->geometryRecursionBlock = false; + } + QAbstractItemView::updateGeometries(); +} + +/*! + Returns the size hint for the \a column's width or -1 if there is no + model. + + If you need to set the width of a given column to a fixed value, call + QHeaderView::resizeSection() on the view's header. + + If you reimplement this function in a subclass, note that the value you + return is only used when resizeColumnToContents() is called. In that case, + if a larger column width is required by either the view's header or + the item delegate, that width will be used instead. + + \sa QWidget::sizeHint, header() +*/ +int QTreeView::sizeHintForColumn(int column) const +{ + Q_D(const QTreeView); + d->executePostedLayout(); + if (d->viewItems.isEmpty()) + return -1; + int w = 0; + QStyleOptionViewItemV4 option = d->viewOptionsV4(); + const QVector<QTreeViewItem> viewItems = d->viewItems; + + int start = 0; + int end = viewItems.count(); + if(end > 1000) { //if we have too many item this function would be too slow. + //we get a good approximation by only iterate over 1000 items. + start = qMax(0, d->firstVisibleItem() - 100); + end = qMin(end, start + 900); + } + + for (int i = start; i < end; ++i) { + if (viewItems.at(i).spanning) + continue; // we have no good size hint + QModelIndex index = viewItems.at(i).index; + index = index.sibling(index.row(), column); + QWidget *editor = d->editorForIndex(index).editor; + if (editor && d->persistent.contains(editor)) { + w = qMax(w, editor->sizeHint().width()); + int min = editor->minimumSize().width(); + int max = editor->maximumSize().width(); + w = qBound(min, w, max); + } + int hint = d->delegateForIndex(index)->sizeHint(option, index).width(); + w = qMax(w, hint + (column == 0 ? d->indentationForItem(i) : 0)); + } + return w; +} + +/*! + Returns the size hint for the row indicated by \a index. + + \sa sizeHintForColumn(), uniformRowHeights() +*/ +int QTreeView::indexRowSizeHint(const QModelIndex &index) const +{ + Q_D(const QTreeView); + if (!d->isIndexValid(index) || !d->itemDelegate) + return 0; + + int start = -1; + int end = -1; + int count = d->header->count(); + bool emptyHeader = (count == 0); + QModelIndex parent = index.parent(); + + if (count && isVisible()) { + // If the sections have moved, we end up checking too many or too few + start = d->header->visualIndexAt(0); + } else { + // If the header has not been laid out yet, we use the model directly + count = d->model->columnCount(parent); + } + + if (isRightToLeft()) { + start = (start == -1 ? count - 1 : start); + end = (end == -1 ? 0 : end); + } else { + start = (start == -1 ? 0 : start); + end = (end == -1 ? count - 1 : end); + } + + int tmp = start; + start = qMin(start, end); + end = qMax(tmp, end); + + int height = -1; + QStyleOptionViewItemV4 option = d->viewOptionsV4(); + // ### If we want word wrapping in the items, + // ### we need to go through all the columns + // ### and set the width of the column + + // Hack to speed up the function + option.rect.setWidth(-1); + + for (int column = start; column <= end; ++column) { + int logicalColumn = emptyHeader ? column : d->header->logicalIndex(column); + if (d->header->isSectionHidden(logicalColumn)) + continue; + QModelIndex idx = d->model->index(index.row(), logicalColumn, parent); + if (idx.isValid()) { + QWidget *editor = d->editorForIndex(idx).editor; + if (editor && d->persistent.contains(editor)) { + height = qMax(height, editor->sizeHint().height()); + int min = editor->minimumSize().height(); + int max = editor->maximumSize().height(); + height = qBound(min, height, max); + } + int hint = d->delegateForIndex(idx)->sizeHint(option, idx).height(); + height = qMax(height, hint); + } + } + + return height; +} + +/*! + \since 4.3 + Returns the height of the row indicated by the given \a index. + \sa indexRowSizeHint() +*/ +int QTreeView::rowHeight(const QModelIndex &index) const +{ + Q_D(const QTreeView); + d->executePostedLayout(); + int i = d->viewIndex(index); + if (i == -1) + return 0; + return d->itemHeight(i); +} + +/*! + \reimp +*/ +void QTreeView::horizontalScrollbarAction(int action) +{ + QAbstractItemView::horizontalScrollbarAction(action); +} + +/*! + \reimp +*/ +bool QTreeView::isIndexHidden(const QModelIndex &index) const +{ + return (isColumnHidden(index.column()) || isRowHidden(index.row(), index.parent())); +} + +/* + private implementation +*/ +void QTreeViewPrivate::initialize() +{ + Q_Q(QTreeView); + updateStyledFrameWidths(); + q->setSelectionBehavior(QAbstractItemView::SelectRows); + q->setSelectionMode(QAbstractItemView::SingleSelection); + q->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); + q->setAttribute(Qt::WA_MacShowFocusRect); + + QHeaderView *header = new QHeaderView(Qt::Horizontal, q); + header->setMovable(true); + header->setStretchLastSection(true); + header->setDefaultAlignment(Qt::AlignLeft|Qt::AlignVCenter); + q->setHeader(header); + + // animation + QObject::connect(&timeline, SIGNAL(frameChanged(int)), q, SLOT(_q_animate())); + QObject::connect(&timeline, SIGNAL(finished()), q, SLOT(_q_endAnimatedOperation()), Qt::QueuedConnection); +} + +void QTreeViewPrivate::expand(int item, bool emitSignal) +{ + Q_Q(QTreeView); + + if (item == -1 || viewItems.at(item).expanded) + return; + + if (emitSignal && animationsEnabled) + prepareAnimatedOperation(item, AnimatedOperation::Expand); + + QAbstractItemView::State oldState = q->state(); + q->setState(QAbstractItemView::ExpandingState); + const QModelIndex index = viewItems.at(item).index; + storeExpanded(index); + viewItems[item].expanded = true; + layout(item); + q->setState(oldState); + + if (emitSignal) { + emit q->expanded(index); + if (animationsEnabled) + beginAnimatedOperation(); + } + if (model->canFetchMore(index)) + model->fetchMore(index); +} + +void QTreeViewPrivate::collapse(int item, bool emitSignal) +{ + Q_Q(QTreeView); + + if (item == -1 || expandedIndexes.isEmpty()) + return; + + //if the current item is now invisible, the autoscroll will expand the tree to see it, so disable the autoscroll + delayedAutoScroll.stop(); + + int total = viewItems.at(item).total; + const QModelIndex &modelIndex = viewItems.at(item).index; + if (!isPersistent(modelIndex)) + return; // if the index is not persistent, no chances it is expanded + QSet<QPersistentModelIndex>::iterator it = expandedIndexes.find(modelIndex); + if (it == expandedIndexes.end() || viewItems.at(item).expanded == false) + return; // nothing to do + + if (emitSignal && animationsEnabled) + prepareAnimatedOperation(item, AnimatedOperation::Collapse); + + QAbstractItemView::State oldState = q->state(); + q->setState(QAbstractItemView::CollapsingState); + expandedIndexes.erase(it); + viewItems[item].expanded = false; + int index = item; + QModelIndex parent = modelIndex; + while (parent.isValid() && parent != root) { + Q_ASSERT(index > -1); + viewItems[index].total -= total; + parent = parent.parent(); + index = viewIndex(parent); + } + viewItems.remove(item + 1, total); // collapse + q->setState(oldState); + + if (emitSignal) { + emit q->collapsed(modelIndex); + if (animationsEnabled) + beginAnimatedOperation(); + } +} + +void QTreeViewPrivate::prepareAnimatedOperation(int item, AnimatedOperation::Type type) +{ + animatedOperation.item = item; + animatedOperation.type = type; + + int top = coordinateForItem(item) + itemHeight(item); + QRect rect = viewport->rect(); + rect.setTop(top); + if (type == AnimatedOperation::Collapse) { + const int limit = rect.height() * 2; + int h = 0; + int c = item + viewItems.at(item).total + 1; + for (int i = item + 1; i < c && h < limit; ++i) + h += itemHeight(i); + rect.setHeight(h); + animatedOperation.duration = h; + } + animatedOperation.top = top; + animatedOperation.before = renderTreeToPixmapForAnimation(rect); +} + +void QTreeViewPrivate::beginAnimatedOperation() +{ + Q_Q(QTreeView); + + QRect rect = viewport->rect(); + rect.setTop(animatedOperation.top); + if (animatedOperation.type == AnimatedOperation::Expand) { + const int limit = rect.height() * 2; + int h = 0; + int c = animatedOperation.item + viewItems.at(animatedOperation.item).total + 1; + for (int i = animatedOperation.item + 1; i < c && h < limit; ++i) + h += itemHeight(i); + rect.setHeight(h); + animatedOperation.duration = h; + } + + animatedOperation.after = renderTreeToPixmapForAnimation(rect); + + q->setState(QAbstractItemView::AnimatingState); + + timeline.stop(); + timeline.setDuration(250); + timeline.setFrameRange(animatedOperation.top, animatedOperation.top + animatedOperation.duration); + timeline.start(); +} + +void QTreeViewPrivate::_q_endAnimatedOperation() +{ + Q_Q(QTreeView); + animatedOperation.before = QPixmap(); + animatedOperation.after = QPixmap(); + q->setState(QAbstractItemView::NoState); + q->updateGeometries(); + viewport->update(); +} + +void QTreeViewPrivate::_q_animate() +{ + QRect rect = viewport->rect(); + rect.moveTop(animatedOperation.top); + viewport->repaint(rect); +} + +void QTreeViewPrivate::drawAnimatedOperation(QPainter *painter) const +{ + int start = timeline.startFrame(); + int end = timeline.endFrame(); + bool collapsing = animatedOperation.type == AnimatedOperation::Collapse; + int current = collapsing ? end - timeline.currentFrame() + start : timeline.currentFrame(); + const QPixmap top = collapsing ? animatedOperation.before : animatedOperation.after; + painter->drawPixmap(0, start, top, 0, end - current - 1, top.width(), top.height()); + const QPixmap bottom = collapsing ? animatedOperation.after : animatedOperation.before; + painter->drawPixmap(0, current, bottom); +} + +QPixmap QTreeViewPrivate::renderTreeToPixmapForAnimation(const QRect &rect) const +{ + Q_Q(const QTreeView); + QPixmap pixmap(rect.size()); + pixmap.fill(Qt::transparent); //the base might not be opaque, and we don't want uninitialized pixels. + QPainter painter(&pixmap); + painter.fillRect(QRect(QPoint(0,0), rect.size()), q->palette().base()); + painter.translate(0, -rect.top()); + q->drawTree(&painter, QRegion(rect)); + painter.end(); + + //and now let's render the editors the editors + QStyleOptionViewItemV4 option = viewOptionsV4(); + for (QList<QEditorInfo>::const_iterator it = editors.constBegin(); it != editors.constEnd(); ++it) { + QWidget *editor = it->editor; + QModelIndex index = it->index; + option.rect = q->visualRect(index); + if (option.rect.isValid()) { + + if (QAbstractItemDelegate *delegate = delegateForIndex(index)) + delegate->updateEditorGeometry(editor, option, index); + + const QPoint pos = editor->pos(); + if (rect.contains(pos)) { + editor->render(&pixmap, pos - rect.topLeft()); + //the animation uses pixmap to display the treeview's content + //the editor is rendered on this pixmap and thus can (should) be hidden + editor->hide(); + } + } + } + + + return pixmap; +} + +void QTreeViewPrivate::_q_currentChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ + Q_Q(QTreeView); + if (previous.isValid()) { + QRect previousRect = q->visualRect(previous); + if (allColumnsShowFocus) { + previousRect.setX(0); + previousRect.setWidth(viewport->width()); + } + viewport->update(previousRect); + } + if (current.isValid()) { + QRect currentRect = q->visualRect(current); + if (allColumnsShowFocus) { + currentRect.setX(0); + currentRect.setWidth(viewport->width()); + } + viewport->update(currentRect); + } +} + +void QTreeViewPrivate::_q_modelAboutToBeReset() +{ + viewItems.clear(); +} + +void QTreeViewPrivate::_q_columnsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + Q_UNUSED(parent); + if (start <= 0 && 0 <= end) + viewItems.clear(); +} + +void QTreeViewPrivate::_q_columnsRemoved(const QModelIndex &parent, int start, int end) +{ + Q_UNUSED(parent); + if (start <= 0 && 0 <= end) + doDelayedItemsLayout(); +} + +void QTreeViewPrivate::layout(int i) +{ + Q_Q(QTreeView); + QModelIndex current; + QModelIndex parent = (i < 0) ? (QModelIndex)root : modelIndex(i); + // modelIndex() will return an index that don't have a parent if column 0 is hidden, + // so we must make sure that parent points to the actual parent that has children. + if (parent != root) + parent = model->index(parent.row(), 0, parent.parent()); + + if (i>=0 && !parent.isValid()) { + //modelIndex() should never return something invalid for the real items. + //This can happen if columncount has been set to 0. + //To avoid infinite loop we stop here. + return; + } + + int count = 0; + if (model->hasChildren(parent)) { + if (model->canFetchMore(parent)) + model->fetchMore(parent); + count = model->rowCount(parent); + } + + bool expanding = true; + if (i == -1) { + if (uniformRowHeights) { + QModelIndex index = model->index(0, 0, parent); + defaultItemHeight = q->indexRowSizeHint(index); + } + viewItems.resize(count); + } else if (viewItems[i].total != (uint)count) { + viewItems.insert(i + 1, count, QTreeViewItem()); // expand + } else { + expanding = false; + } + + int first = i + 1; + int level = (i >= 0 ? viewItems.at(i).level + 1 : 0); + int hidden = 0; + int last = 0; + int children = 0; + + int firstColumn = 0; + while (header->isSectionHidden(firstColumn) && firstColumn < header->count()) + ++firstColumn; + + for (int j = first; j < first + count; ++j) { + current = model->index(j - first, firstColumn, parent); + if (isRowHidden(current.sibling(current.row(), 0))) { + ++hidden; + last = j - hidden + children; + } else { + last = j - hidden + children; + viewItems[last].index = current; + viewItems[last].level = level; + viewItems[last].height = 0; + viewItems[last].spanning = q->isFirstColumnSpanned(current.row(), parent); + viewItems[last].expanded = false; + viewItems[last].total = 0; + if (isIndexExpanded(current)) { + viewItems[last].expanded = true; + layout(last); + children += viewItems[last].total; + last = j - hidden + children; + } + } + } + + // remove hidden items + if (hidden > 0) + viewItems.remove(last + 1, hidden); // collapse + + if (!expanding) + return; // nothing changed + + while (parent != root) { + Q_ASSERT(i > -1); + viewItems[i].total += count - hidden; + parent = parent.parent(); + i = viewIndex(parent); + } +} + +int QTreeViewPrivate::pageUp(int i) const +{ + int index = itemAtCoordinate(coordinateForItem(i) - viewport->height()); + return index == -1 ? 0 : index; +} + +int QTreeViewPrivate::pageDown(int i) const +{ + int index = itemAtCoordinate(coordinateForItem(i) + viewport->height()); + return index == -1 ? viewItems.count() - 1 : index; +} + +int QTreeViewPrivate::indentationForItem(int item) const +{ + if (item < 0 || item >= viewItems.count()) + return 0; + int level = viewItems.at(item).level; + if (rootDecoration) + ++level; + return level * indent; +} + +int QTreeViewPrivate::itemHeight(int item) const +{ + if (uniformRowHeights) + return defaultItemHeight; + if (viewItems.isEmpty()) + return 0; + const QModelIndex &index = viewItems.at(item).index; + int height = viewItems.at(item).height; + if (height <= 0 && index.isValid()) { + height = q_func()->indexRowSizeHint(index); + viewItems[item].height = height; + } + if (!index.isValid() || height < 0) + return 0; + return height; +} + + +/*! + \internal + Returns the viewport y coordinate for \a item. +*/ +int QTreeViewPrivate::coordinateForItem(int item) const +{ + Q_Q(const QTreeView); + if (verticalScrollMode == QAbstractItemView::ScrollPerPixel) { + if (uniformRowHeights) + return (item * defaultItemHeight) - q->verticalScrollBar()->value(); + // ### optimize (spans or caching) + int y = 0; + for (int i = 0; i < viewItems.count(); ++i) { + if (i == item) + return y - q->verticalScrollBar()->value(); + y += itemHeight(i); + } + } else { // ScrollPerItem + int topViewItemIndex = q->verticalScrollBar()->value(); + if (uniformRowHeights) + return defaultItemHeight * (item - topViewItemIndex); + if (item >= topViewItemIndex) { + // search in the visible area first and continue down + // ### slow if the item is not visible + int viewItemCoordinate = 0; + int viewItemIndex = topViewItemIndex; + while (viewItemIndex < viewItems.count()) { + if (viewItemIndex == item) + return viewItemCoordinate; + viewItemCoordinate += itemHeight(viewItemIndex); + ++viewItemIndex; + } + // below the last item in the view + Q_ASSERT(false); + return viewItemCoordinate; + } else { + // search the area above the viewport (used for editor widgets) + int viewItemCoordinate = 0; + for (int viewItemIndex = topViewItemIndex; viewItemIndex > 0; --viewItemIndex) { + if (viewItemIndex == item) + return viewItemCoordinate; + viewItemCoordinate -= itemHeight(viewItemIndex - 1); + } + return viewItemCoordinate; + } + } + return 0; +} + +/*! + \internal + Returns the index of the view item at the + given viewport \a coordinate. + + \sa modelIndex() +*/ +int QTreeViewPrivate::itemAtCoordinate(int coordinate) const +{ + Q_Q(const QTreeView); + const int itemCount = viewItems.count(); + if (itemCount == 0) + return -1; + if (uniformRowHeights && defaultItemHeight <= 0) + return -1; + if (verticalScrollMode == QAbstractItemView::ScrollPerPixel) { + if (uniformRowHeights) { + const int viewItemIndex = (coordinate + q->verticalScrollBar()->value()) / defaultItemHeight; + return ((viewItemIndex >= itemCount || viewItemIndex < 0) ? -1 : viewItemIndex); + } + // ### optimize + int viewItemCoordinate = 0; + const int contentsCoordinate = coordinate + q->verticalScrollBar()->value(); + for (int viewItemIndex = 0; viewItemIndex < viewItems.count(); ++viewItemIndex) { + viewItemCoordinate += itemHeight(viewItemIndex); + if (viewItemCoordinate >= contentsCoordinate) + return (viewItemIndex >= itemCount ? -1 : viewItemIndex); + } + } else { // ScrollPerItem + int topViewItemIndex = q->verticalScrollBar()->value(); + if (uniformRowHeights) { + if (coordinate < 0) + coordinate -= defaultItemHeight - 1; + const int viewItemIndex = topViewItemIndex + (coordinate / defaultItemHeight); + return ((viewItemIndex >= itemCount || viewItemIndex < 0) ? -1 : viewItemIndex); + } + if (coordinate >= 0) { + // the coordinate is in or below the viewport + int viewItemCoordinate = 0; + for (int viewItemIndex = topViewItemIndex; viewItemIndex < viewItems.count(); ++viewItemIndex) { + viewItemCoordinate += itemHeight(viewItemIndex); + if (viewItemCoordinate > coordinate) + return (viewItemIndex >= itemCount ? -1 : viewItemIndex); + } + } else { + // the coordinate is above the viewport + int viewItemCoordinate = 0; + for (int viewItemIndex = topViewItemIndex; viewItemIndex >= 0; --viewItemIndex) { + if (viewItemCoordinate <= coordinate) + return (viewItemIndex >= itemCount ? -1 : viewItemIndex); + viewItemCoordinate -= itemHeight(viewItemIndex); + } + } + } + return -1; +} + +int QTreeViewPrivate::viewIndex(const QModelIndex &_index) const +{ + Q_Q(const QTreeView); + if (!_index.isValid() || viewItems.isEmpty()) + return -1; + + const int totalCount = viewItems.count(); + int firstColumn = 0; + while (q->isColumnHidden(firstColumn) && firstColumn < header->count()) + ++firstColumn; + const QModelIndex index = _index.sibling(_index.row(), firstColumn); + + + // A quick check near the last item to see if we are just incrementing + const int start = lastViewedItem > 2 ? lastViewedItem - 2 : 0; + const int end = lastViewedItem < totalCount - 2 ? lastViewedItem + 2 : totalCount; + int row = index.row(); + for (int i = start; i < end; ++i) { + const QModelIndex &idx = viewItems.at(i).index; + if (idx.row() == row) { + if (idx.internalId() == index.internalId()) { + lastViewedItem = i; + return i; + } + } + } + + // NOTE: this function is slow if the item is outside the visible area + // search in visible items first and below + int t = firstVisibleItem(); + t = t > 100 ? t - 100 : 0; // start 100 items above the visible area + + for (int i = t; i < totalCount; ++i) { + const QModelIndex &idx = viewItems.at(i).index; + if (idx.row() == row) { + if (idx.internalId() == index.internalId()) { + lastViewedItem = i; + return i; + } + } + } + // search from top to first visible + for (int j = 0; j < t; ++j) { + const QModelIndex &idx = viewItems.at(j).index; + if (idx.row() == row) { + if (idx.internalId() == index.internalId()) { + lastViewedItem = j; + return j; + } + } + } + // nothing found + return -1; +} + +QModelIndex QTreeViewPrivate::modelIndex(int i, int column) const +{ + if (i < 0 || i >= viewItems.count()) + return QModelIndex(); + + QModelIndex ret = viewItems.at(i).index; + if (column) + ret = ret.sibling(ret.row(), column); + return ret; +} + +int QTreeViewPrivate::firstVisibleItem(int *offset) const +{ + Q_Q(const QTreeView); + const int value = q->verticalScrollBar()->value(); + if (verticalScrollMode == QAbstractItemView::ScrollPerItem) { + if (offset) + *offset = 0; + return (value < 0 || value >= viewItems.count()) ? -1 : value; + } + // ScrollMode == ScrollPerPixel + if (uniformRowHeights) { + if (!defaultItemHeight) + return -1; + + if (offset) + *offset = -(value % defaultItemHeight); + return value / defaultItemHeight; + } + int y = 0; // ### optimize (use spans ?) + for (int i = 0; i < viewItems.count(); ++i) { + y += itemHeight(i); // the height value is cached + if (y > value) { + if (offset) + *offset = y - value - itemHeight(i); + return i; + } + } + return -1; +} + +int QTreeViewPrivate::columnAt(int x) const +{ + return header->logicalIndexAt(x); +} + +void QTreeViewPrivate::relayout(const QModelIndex &parent) +{ + Q_Q(QTreeView); + // do a local relayout of the items + if (parent.isValid()) { + int parentViewIndex = viewIndex(parent); + if (parentViewIndex > -1 && viewItems.at(parentViewIndex).expanded) { + collapse(parentViewIndex, false); // remove the current layout + expand(parentViewIndex, false); // do the relayout + q->updateGeometries(); + viewport->update(); + } + } else { + viewItems.clear(); + q->doItemsLayout(); + } +} + + +void QTreeViewPrivate::updateScrollBars() +{ + Q_Q(QTreeView); + QSize viewportSize = viewport->size(); + if (!viewportSize.isValid()) + viewportSize = QSize(0, 0); + + int itemsInViewport = 0; + if (uniformRowHeights) { + if (defaultItemHeight <= 0) + itemsInViewport = viewItems.count(); + else + itemsInViewport = viewportSize.height() / defaultItemHeight; + } else { + const int itemsCount = viewItems.count(); + const int viewportHeight = viewportSize.height(); + for (int height = 0, item = itemsCount - 1; item >= 0; --item) { + height += itemHeight(item); + if (height > viewportHeight) + break; + ++itemsInViewport; + } + } + if (verticalScrollMode == QAbstractItemView::ScrollPerItem) { + if (!viewItems.isEmpty()) + itemsInViewport = qMax(1, itemsInViewport); + q->verticalScrollBar()->setRange(0, viewItems.count() - itemsInViewport); + q->verticalScrollBar()->setPageStep(itemsInViewport); + q->verticalScrollBar()->setSingleStep(1); + } else { // scroll per pixel + int contentsHeight = 0; + if (uniformRowHeights) { + contentsHeight = defaultItemHeight * viewItems.count(); + } else { // ### optimize (spans or caching) + for (int i = 0; i < viewItems.count(); ++i) + contentsHeight += itemHeight(i); + } + q->verticalScrollBar()->setRange(0, contentsHeight - viewportSize.height()); + q->verticalScrollBar()->setPageStep(viewportSize.height()); + q->verticalScrollBar()->setSingleStep(qMax(viewportSize.height() / (itemsInViewport + 1), 2)); + } + + const int columnCount = header->count(); + const int viewportWidth = viewportSize.width(); + int columnsInViewport = 0; + for (int width = 0, column = columnCount - 1; column >= 0; --column) { + int logical = header->logicalIndex(column); + width += header->sectionSize(logical); + if (width > viewportWidth) + break; + ++columnsInViewport; + } + if (columnCount > 0) + columnsInViewport = qMax(1, columnsInViewport); + if (horizontalScrollMode == QAbstractItemView::ScrollPerItem) { + q->horizontalScrollBar()->setRange(0, columnCount - columnsInViewport); + q->horizontalScrollBar()->setPageStep(columnsInViewport); + q->horizontalScrollBar()->setSingleStep(1); + } else { // scroll per pixel + const int horizontalLength = header->length(); + const QSize maxSize = q->maximumViewportSize(); + if (maxSize.width() >= horizontalLength && q->verticalScrollBar()->maximum() <= 0) + viewportSize = maxSize; + q->horizontalScrollBar()->setPageStep(viewportSize.width()); + q->horizontalScrollBar()->setRange(0, qMax(horizontalLength - viewportSize.width(), 0)); + q->horizontalScrollBar()->setSingleStep(qMax(viewportSize.width() / (columnsInViewport + 1), 2)); + } +} + +int QTreeViewPrivate::itemDecorationAt(const QPoint &pos) const +{ + int x = pos.x(); + int column = header->logicalIndexAt(x); + if (column != 0) + return -1; // no logical index at x + + int viewItemIndex = itemAtCoordinate(pos.y()); + QRect returning = itemDecorationRect(modelIndex(viewItemIndex)); + if (!returning.contains(pos)) + return -1; + + return viewItemIndex; +} + +QRect QTreeViewPrivate::itemDecorationRect(const QModelIndex &index) const +{ + Q_Q(const QTreeView); + if (!rootDecoration && index.parent() == root) + return QRect(); // no decoration at root + + int viewItemIndex = viewIndex(index); + if (viewItemIndex < 0 || !hasVisibleChildren(viewItems.at(viewItemIndex).index)) + return QRect(); + + int itemIndentation = indentationForItem(viewItemIndex); + int position = header->sectionViewportPosition(0); + int size = header->sectionSize(0); + + QRect rect; + if (q->isRightToLeft()) + rect = QRect(position + size - itemIndentation, coordinateForItem(viewItemIndex), + indent, itemHeight(viewItemIndex)); + else + rect = QRect(position + itemIndentation - indent, coordinateForItem(viewItemIndex), + indent, itemHeight(viewItemIndex)); + QStyleOption opt; + opt.initFrom(q); + opt.rect = rect; + return q->style()->subElementRect(QStyle::SE_TreeViewDisclosureItem, &opt, q); +} + +QList<QPair<int, int> > QTreeViewPrivate::columnRanges(const QModelIndex &topIndex, + const QModelIndex &bottomIndex) const +{ + const int topVisual = header->visualIndex(topIndex.column()), + bottomVisual = header->visualIndex(bottomIndex.column()); + + const int start = qMin(topVisual, bottomVisual); + const int end = qMax(topVisual, bottomVisual); + + QList<int> logicalIndexes; + + //we iterate over the visual indexes to get the logical indexes + for (int c = start; c <= end; c++) { + const int logical = header->logicalIndex(c); + if (!header->isSectionHidden(logical)) { + logicalIndexes << logical; + } + } + //let's sort the list + qSort(logicalIndexes.begin(), logicalIndexes.end()); + + QList<QPair<int, int> > ret; + QPair<int, int> current; + current.first = -2; // -1 is not enough because -1+1 = 0 + current.second = -2; + foreach (int logicalColumn, logicalIndexes) { + if (current.second + 1 != logicalColumn) { + if (current.first != -2) { + //let's save the current one + ret += current; + } + //let's start a new one + current.first = current.second = logicalColumn; + } else { + current.second++; + } + } + + //let's get the last range + if (current.first != -2) { + ret += current; + } + + return ret; +} + +void QTreeViewPrivate::select(const QModelIndex &topIndex, const QModelIndex &bottomIndex, + QItemSelectionModel::SelectionFlags command) +{ + Q_Q(QTreeView); + QItemSelection selection; + const int top = viewIndex(topIndex), + bottom = viewIndex(bottomIndex); + + const QList< QPair<int, int> > colRanges = columnRanges(topIndex, bottomIndex); + QList< QPair<int, int> >::const_iterator it; + for (it = colRanges.begin(); it != colRanges.end(); ++it) { + const int left = (*it).first, + right = (*it).second; + + QModelIndex previous; + QItemSelectionRange currentRange; + QStack<QItemSelectionRange> rangeStack; + for (int i = top; i <= bottom; ++i) { + QModelIndex index = modelIndex(i); + QModelIndex parent = index.parent(); + QModelIndex previousParent = previous.parent(); + if (previous.isValid() && parent == previousParent) { + // same parent + if (qAbs(previous.row() - index.row()) > 1) { + //a hole (hidden index inside a range) has been detected + if (currentRange.isValid()) { + selection.append(currentRange); + } + //let's start a new range + currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right)); + } else { + QModelIndex tl = model->index(currentRange.top(), currentRange.left(), + currentRange.parent()); + currentRange = QItemSelectionRange(tl, index.sibling(index.row(), right)); + } + } else if (previous.isValid() && parent == model->index(previous.row(), 0, previousParent)) { + // item is child of previous + rangeStack.push(currentRange); + currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right)); + } else { + if (currentRange.isValid()) + selection.append(currentRange); + if (rangeStack.isEmpty()) { + currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right)); + } else { + currentRange = rangeStack.pop(); + index = currentRange.bottomRight(); //let's resume the range + --i; //we process again the current item + } + } + previous = index; + } + if (currentRange.isValid()) + selection.append(currentRange); + for (int i = 0; i < rangeStack.count(); ++i) + selection.append(rangeStack.at(i)); + } + q->selectionModel()->select(selection, command); +} + +QPair<int,int> QTreeViewPrivate::startAndEndColumns(const QRect &rect) const +{ + Q_Q(const QTreeView); + int start = header->visualIndexAt(rect.left()); + int end = header->visualIndexAt(rect.right()); + if (q->isRightToLeft()) { + start = (start == -1 ? header->count() - 1 : start); + end = (end == -1 ? 0 : end); + } else { + start = (start == -1 ? 0 : start); + end = (end == -1 ? header->count() - 1 : end); + } + return qMakePair<int,int>(qMin(start, end), qMax(start, end)); +} + +bool QTreeViewPrivate::hasVisibleChildren(const QModelIndex& parent) const +{ + Q_Q(const QTreeView); + if (model->hasChildren(parent)) { + if (hiddenIndexes.isEmpty()) + return true; + if (q->isIndexHidden(parent)) + return false; + int rowCount = model->rowCount(parent); + for (int i = 0; i < rowCount; ++i) { + if (!q->isRowHidden(i, parent)) + return true; + } + if (rowCount == 0) + return true; + } + return false; +} + +void QTreeViewPrivate::rowsRemoved(const QModelIndex &parent, + int start, int end, bool after) +{ + Q_Q(QTreeView); + // if we are going to do a complete relayout anyway, there is no need to update + if (delayedPendingLayout) { + _q_rowsRemoved(parent, start, end); + return; + } + + const int parentItem = viewIndex(parent); + if ((parentItem != -1) || (parent == root)) { + + const uint childLevel = (parentItem == -1) + ? uint(0) : viewItems.at(parentItem).level + 1; + Q_UNUSED(childLevel); // unused in release mode, used in assert below + + const int firstChildItem = parentItem + 1; + int lastChildItem = firstChildItem + ((parentItem == -1) + ? viewItems.count() + : viewItems.at(parentItem).total) - 1; + + const int delta = end - start + 1; + + int removedCount = 0; + for (int item = firstChildItem; item <= lastChildItem; ) { + Q_ASSERT(viewItems.at(item).level == childLevel); + const QModelIndex modelIndex = viewItems.at(item).index; + //Q_ASSERT(modelIndex.parent() == parent); + const int count = viewItems.at(item).total + 1; + if (modelIndex.row() < start) { + // not affected by the removal + item += count; + } else if (modelIndex.row() <= end) { + // removed + viewItems.remove(item, count); + removedCount += count; + lastChildItem -= count; + } else { + if (after) { + // moved; update the model index + viewItems[item].index = model->index( + modelIndex.row() - delta, modelIndex.column(), parent); + } + item += count; + } + } + + updateChildCount(parentItem, -removedCount); + if (after) { + q->updateGeometries(); + viewport->update(); + } else { + //we have removed items: we should at least update the scroll bar values. + // They are used to determine the item geometry. + updateScrollBars(); + } + } else { + // If an ancestor of root is removed then relayout + QModelIndex idx = root; + while (idx.isValid()) { + idx = idx.parent(); + if (idx == parent) { + doDelayedItemsLayout(); + break; + } + } + } + _q_rowsRemoved(parent, start, end); + + QSet<QPersistentModelIndex>::iterator it = expandedIndexes.begin(); + while (it != expandedIndexes.constEnd()) { + if (!it->isValid()) + it = expandedIndexes.erase(it); + else + ++it; + } + it = hiddenIndexes.begin(); + while (it != hiddenIndexes.constEnd()) { + if (!it->isValid()) + it = hiddenIndexes.erase(it); + else + ++it; + } +} + +void QTreeViewPrivate::updateChildCount(const int parentItem, const int delta) +{ + if ((parentItem != -1) && delta) { + int level = viewItems.at(parentItem).level; + int item = parentItem; + do { + Q_ASSERT(item >= 0); + for ( ; int(viewItems.at(item).level) != level; --item) ; + viewItems[item].total += delta; + --level; + } while (level >= 0); + } +} + + +void QTreeViewPrivate::_q_sortIndicatorChanged(int column, Qt::SortOrder order) +{ + model->sort(column, order); +} + +/*! + \reimp + */ +void QTreeView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ +#ifndef QT_NO_ACCESSIBILITY + if (QAccessible::isActive()) { + int entry = visualIndex(current) + 1; + if (header()) + ++entry; + QAccessible::updateAccessibility(viewport(), entry, QAccessible::Focus); + } +#endif + QAbstractItemView::currentChanged(current, previous); +} + +/*! + \reimp + */ +void QTreeView::selectionChanged(const QItemSelection &selected, + const QItemSelection &deselected) +{ +#ifndef QT_NO_ACCESSIBILITY + if (QAccessible::isActive()) { + // ### does not work properly for selection ranges. + QModelIndex sel = selected.indexes().value(0); + if (sel.isValid()) { + int entry = visualIndex(sel) + 1; + if (header()) + ++entry; + QAccessible::updateAccessibility(viewport(), entry, QAccessible::Selection); + } + QModelIndex desel = deselected.indexes().value(0); + if (desel.isValid()) { + int entry = visualIndex(desel) + 1; + if (header()) + ++entry; + QAccessible::updateAccessibility(viewport(), entry, QAccessible::SelectionRemove); + } + } +#endif + QAbstractItemView::selectionChanged(selected, deselected); +} + +int QTreeView::visualIndex(const QModelIndex &index) const +{ + Q_D(const QTreeView); + d->executePostedLayout(); + return d->viewIndex(index); +} + +QT_END_NAMESPACE + +#include "moc_qtreeview.cpp" + +#endif // QT_NO_TREEVIEW diff --git a/src/gui/itemviews/qtreeview.h b/src/gui/itemviews/qtreeview.h new file mode 100644 index 0000000..1e1a362 --- /dev/null +++ b/src/gui/itemviews/qtreeview.h @@ -0,0 +1,241 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTREEVIEW_H +#define QTREEVIEW_H + +#include <QtGui/qabstractitemview.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_TREEVIEW + +class QTreeViewPrivate; +class QHeaderView; + +class Q_GUI_EXPORT QTreeView : public QAbstractItemView +{ + Q_OBJECT + Q_PROPERTY(int autoExpandDelay READ autoExpandDelay WRITE setAutoExpandDelay) + Q_PROPERTY(int indentation READ indentation WRITE setIndentation) + Q_PROPERTY(bool rootIsDecorated READ rootIsDecorated WRITE setRootIsDecorated) + Q_PROPERTY(bool uniformRowHeights READ uniformRowHeights WRITE setUniformRowHeights) + Q_PROPERTY(bool itemsExpandable READ itemsExpandable WRITE setItemsExpandable) + Q_PROPERTY(bool sortingEnabled READ isSortingEnabled WRITE setSortingEnabled) + Q_PROPERTY(bool animated READ isAnimated WRITE setAnimated) + Q_PROPERTY(bool allColumnsShowFocus READ allColumnsShowFocus WRITE setAllColumnsShowFocus) + Q_PROPERTY(bool wordWrap READ wordWrap WRITE setWordWrap) + Q_PROPERTY(bool headerHidden READ isHeaderHidden WRITE setHeaderHidden) + Q_PROPERTY(bool expandsOnDoubleClick READ expandsOnDoubleClick WRITE setExpandsOnDoubleClick) + +public: + explicit QTreeView(QWidget *parent = 0); + ~QTreeView(); + + void setModel(QAbstractItemModel *model); + void setRootIndex(const QModelIndex &index); + void setSelectionModel(QItemSelectionModel *selectionModel); + + QHeaderView *header() const; + void setHeader(QHeaderView *header); + + int autoExpandDelay() const; + void setAutoExpandDelay(int delay); + + int indentation() const; + void setIndentation(int i); + + bool rootIsDecorated() const; + void setRootIsDecorated(bool show); + + bool uniformRowHeights() const; + void setUniformRowHeights(bool uniform); + + bool itemsExpandable() const; + void setItemsExpandable(bool enable); + + bool expandsOnDoubleClick() const; + void setExpandsOnDoubleClick(bool enable); + + int columnViewportPosition(int column) const; + int columnWidth(int column) const; + void setColumnWidth(int column, int width); + int columnAt(int x) const; + + bool isColumnHidden(int column) const; + void setColumnHidden(int column, bool hide); + + bool isHeaderHidden() const; + void setHeaderHidden(bool hide); + + bool isRowHidden(int row, const QModelIndex &parent) const; + void setRowHidden(int row, const QModelIndex &parent, bool hide); + + bool isFirstColumnSpanned(int row, const QModelIndex &parent) const; + void setFirstColumnSpanned(int row, const QModelIndex &parent, bool span); + + bool isExpanded(const QModelIndex &index) const; + void setExpanded(const QModelIndex &index, bool expand); + + void setSortingEnabled(bool enable); + bool isSortingEnabled() const; + + void setAnimated(bool enable); + bool isAnimated() const; + + void setAllColumnsShowFocus(bool enable); + bool allColumnsShowFocus() const; + + void setWordWrap(bool on); + bool wordWrap() const; + + void keyboardSearch(const QString &search); + + QRect visualRect(const QModelIndex &index) const; + void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible); + QModelIndex indexAt(const QPoint &p) const; + QModelIndex indexAbove(const QModelIndex &index) const; + QModelIndex indexBelow(const QModelIndex &index) const; + + void doItemsLayout(); + void reset(); + + void sortByColumn(int column, Qt::SortOrder order); + +Q_SIGNALS: + void expanded(const QModelIndex &index); + void collapsed(const QModelIndex &index); + +public Q_SLOTS: + void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void hideColumn(int column); + void showColumn(int column); + void expand(const QModelIndex &index); + void collapse(const QModelIndex &index); + void resizeColumnToContents(int column); + void sortByColumn(int column); + void selectAll(); + void expandAll(); + void collapseAll(); + void expandToDepth(int depth); + +protected Q_SLOTS: + void columnResized(int column, int oldSize, int newSize); + void columnCountChanged(int oldCount, int newCount); + void columnMoved(); + void reexpand(); + void rowsRemoved(const QModelIndex &parent, int first, int last); + +protected: + QTreeView(QTreeViewPrivate &dd, QWidget *parent = 0); + void scrollContentsBy(int dx, int dy); + void rowsInserted(const QModelIndex &parent, int start, int end); + void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + + QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers); + int horizontalOffset() const; + int verticalOffset() const; + + void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command); + QRegion visualRegionForSelection(const QItemSelection &selection) const; + QModelIndexList selectedIndexes() const; + + void timerEvent(QTimerEvent *event); + void paintEvent(QPaintEvent *event); + + void drawTree(QPainter *painter, const QRegion ®ion) const; + virtual void drawRow(QPainter *painter, + const QStyleOptionViewItem &options, + const QModelIndex &index) const; + virtual void drawBranches(QPainter *painter, + const QRect &rect, + const QModelIndex &index) const; + + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void mouseDoubleClickEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void keyPressEvent(QKeyEvent *event); +#ifndef QT_NO_DRAGANDDROP + void dragMoveEvent(QDragMoveEvent *event); +#endif + bool viewportEvent(QEvent *event); + + void updateGeometries(); + + int sizeHintForColumn(int column) const; + int indexRowSizeHint(const QModelIndex &index) const; + int rowHeight(const QModelIndex &index) const; + + void horizontalScrollbarAction(int action); + + bool isIndexHidden(const QModelIndex &index) const; + void selectionChanged(const QItemSelection &selected, + const QItemSelection &deselected); + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); + +private: + friend class QAccessibleItemView; + int visualIndex(const QModelIndex &index) const; + + Q_DECLARE_PRIVATE(QTreeView) + Q_DISABLE_COPY(QTreeView) + Q_PRIVATE_SLOT(d_func(), void _q_endAnimatedOperation()) + Q_PRIVATE_SLOT(d_func(), void _q_animate()) + Q_PRIVATE_SLOT(d_func(), void _q_currentChanged(const QModelIndex&, const QModelIndex &)) + Q_PRIVATE_SLOT(d_func(), void _q_columnsAboutToBeRemoved(const QModelIndex &, int, int)) + Q_PRIVATE_SLOT(d_func(), void _q_columnsRemoved(const QModelIndex &, int, int)) + Q_PRIVATE_SLOT(d_func(), void _q_modelAboutToBeReset()) + Q_PRIVATE_SLOT(d_func(), void _q_sortIndicatorChanged(int column, Qt::SortOrder order)) + Q_PRIVATE_SLOT(d_func(), void _q_modelDestroyed()) +}; + +#endif // QT_NO_TREEVIEW + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTREEVIEW_H diff --git a/src/gui/itemviews/qtreeview_p.h b/src/gui/itemviews/qtreeview_p.h new file mode 100644 index 0000000..22155c7 --- /dev/null +++ b/src/gui/itemviews/qtreeview_p.h @@ -0,0 +1,240 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTREEVIEW_P_H +#define QTREEVIEW_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qabstractitemview_p.h" + +#ifndef QT_NO_TREEVIEW + +QT_BEGIN_NAMESPACE + +struct QTreeViewItem +{ + QTreeViewItem() : expanded(false), spanning(false), total(0), level(0), height(0) {} + QModelIndex index; // we remove items whenever the indexes are invalidated + uint expanded : 1; + uint spanning : 1; + uint total : 30; // total number of children visible + uint level : 16; // indentation + int height : 16; // row height +}; + +class QTreeViewPrivate : public QAbstractItemViewPrivate +{ + Q_DECLARE_PUBLIC(QTreeView) +public: + + QTreeViewPrivate() + : QAbstractItemViewPrivate(), + header(0), indent(20), lastViewedItem(0), defaultItemHeight(-1), + uniformRowHeights(false), rootDecoration(true), + itemsExpandable(true), sortingEnabled(false), + expandsOnDoubleClick(true), + allColumnsShowFocus(false), + animationsEnabled(false), columnResizeTimerID(0), + autoExpandDelay(-1), hoverBranch(-1), geometryRecursionBlock(false) {} + + ~QTreeViewPrivate() {} + void initialize(); + + struct AnimatedOperation + { + enum Type { Expand, Collapse }; + int item; + int top; + int duration; + Type type; + QPixmap before; + QPixmap after; + }; + + void expand(int item, bool emitSignal); + void collapse(int item, bool emitSignal); + + void prepareAnimatedOperation(int item, AnimatedOperation::Type type); + void beginAnimatedOperation(); + void _q_endAnimatedOperation(); + void drawAnimatedOperation(QPainter *painter) const; + QPixmap renderTreeToPixmapForAnimation(const QRect &rect) const; + + inline QRect animationRect() const + { return QRect(0, animatedOperation.top, viewport->width(), + viewport->height() - animatedOperation.top); } + + void _q_currentChanged(const QModelIndex&, const QModelIndex&); + void _q_columnsAboutToBeRemoved(const QModelIndex &, int, int); + void _q_columnsRemoved(const QModelIndex &, int, int); + void _q_modelAboutToBeReset(); + void _q_animate(); + void _q_sortIndicatorChanged(int column, Qt::SortOrder order); + void _q_modelDestroyed(); + + void layout(int item); + + int pageUp(int item) const; + int pageDown(int item) const; + + int itemHeight(int item) const; + int indentationForItem(int item) const; + int coordinateForItem(int item) const; + int itemAtCoordinate(int coordinate) const; + + int viewIndex(const QModelIndex &index) const; + QModelIndex modelIndex(int i, int column = 0) const; + + int firstVisibleItem(int *offset = 0) const; + int columnAt(int x) const; + bool hasVisibleChildren( const QModelIndex& parent) const; + + void relayout(const QModelIndex &parent); + bool expandOrCollapseItemAtPos(const QPoint &pos); + + void updateScrollBars(); + + int itemDecorationAt(const QPoint &pos) const; + QRect itemDecorationRect(const QModelIndex &index) const; + + + QList<QPair<int, int> > columnRanges(const QModelIndex &topIndex, const QModelIndex &bottomIndex) const; + void select(const QModelIndex &start, const QModelIndex &stop, QItemSelectionModel::SelectionFlags command); + + QPair<int,int> startAndEndColumns(const QRect &rect) const; + + void updateChildCount(const int parentItem, const int delta); + void rowsRemoved(const QModelIndex &parent, + int start, int end, bool before); + + void paintAlternatingRowColors(QPainter *painter, QStyleOptionViewItemV4 *option, int y, int bottom) const; + + QHeaderView *header; + int indent; + + mutable QVector<QTreeViewItem> viewItems; + mutable int lastViewedItem; + int defaultItemHeight; // this is just a number; contentsHeight() / numItems + bool uniformRowHeights; // used when all rows have the same height + bool rootDecoration; + bool itemsExpandable; + bool sortingEnabled; + bool expandsOnDoubleClick; + bool allColumnsShowFocus; + + // used for drawing + mutable QPair<int,int> leftAndRight; + mutable int current; + mutable bool spanning; + + // used when expanding and collapsing items + QSet<QPersistentModelIndex> expandedIndexes; + QStack<bool> expandParent; + AnimatedOperation animatedOperation; + bool animationsEnabled; + + inline bool storeExpanded(const QPersistentModelIndex &idx) { + if (expandedIndexes.contains(idx)) + return false; + expandedIndexes.insert(idx); + return true; + } + + inline bool isIndexExpanded(const QModelIndex &idx) const { + //We first check if the idx is a QPersistentModelIndex, because creating QPersistentModelIndex is slow + return isPersistent(idx) && expandedIndexes.contains(idx); + } + + // used when hiding and showing items + QSet<QPersistentModelIndex> hiddenIndexes; + + inline bool isRowHidden(const QModelIndex &idx) const { + //We first check if the idx is a QPersistentModelIndex, because creating QPersistentModelIndex is slow + return isPersistent(idx) && hiddenIndexes.contains(idx); + } + + inline bool isItemHiddenOrDisabled(int i) const { + if (i < 0 || i >= viewItems.count()) + return false; + const QModelIndex index = viewItems.at(i).index; + return isRowHidden(index) || !isIndexEnabled(index); + } + + inline int above(int item) const + { int i = item; while (isItemHiddenOrDisabled(--item)){} return item < 0 ? i : item; } + inline int below(int item) const + { int i = item; while (isItemHiddenOrDisabled(++item)){} return item >= viewItems.count() ? i : item; } + inline void invalidateHeightCache(int item) const + { viewItems[item].height = 0; } + + // used for spanning rows + QVector<QPersistentModelIndex> spanningIndexes; + + // used for updating resized columns + int columnResizeTimerID; + QList<int> columnsToUpdate; + + // used for the automatic opening of nodes during DND + int autoExpandDelay; + QBasicTimer openTimer; + + // used for drawing hilighted expand/collapse indicators + int hoverBranch; + + // used for blocking recursion when calling setViewportMargins from updateGeometries + bool geometryRecursionBlock; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_TREEVIEW + +#endif // QTREEVIEW_P_H diff --git a/src/gui/itemviews/qtreewidget.cpp b/src/gui/itemviews/qtreewidget.cpp new file mode 100644 index 0000000..357153d --- /dev/null +++ b/src/gui/itemviews/qtreewidget.cpp @@ -0,0 +1,3434 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtreewidget.h" + +#ifndef QT_NO_TREEWIDGET +#include <qheaderview.h> +#include <qpainter.h> +#include <qitemdelegate.h> +#include <qstack.h> +#include <qdebug.h> +#include <private/qtreewidget_p.h> +#include <private/qwidgetitemdata_p.h> +#include <private/qtreewidgetitemiterator_p.h> + +QT_BEGIN_NAMESPACE + +// workaround for VC++ 6.0 linker bug (?) +typedef bool(*LessThan)(const QPair<QTreeWidgetItem*,int>&,const QPair<QTreeWidgetItem*,int>&); + +class QTreeModelLessThan +{ +public: + inline bool operator()(QTreeWidgetItem *i1, QTreeWidgetItem *i2) const + { return *i1 < *i2; } +}; + +class QTreeModelGreaterThan +{ +public: + inline bool operator()(QTreeWidgetItem *i1, QTreeWidgetItem *i2) const + { return *i2 < *i1; } +}; + +/* + \class QTreeModel + \brief The QTreeModel class manages the items stored in a tree view. + + \ingroup model-view + \mainclass +*/ + +/*! + \enum QTreeWidgetItem::ChildIndicatorPolicy + \since 4.3 + + \value ShowIndicator The controls for expanding and collapsing will be shown for this item even if there are no children. + \value DontShowIndicator The controls for expanding and collapsing will never be shown even if there are children. If the node is forced open the user will not be able to expand or collapse the item. + \value DontShowIndicatorWhenChildless The controls for expanding and collapsing will be shown if the item contains children. +*/ + +/*! + \fn void QTreeWidgetItem::setDisabled(bool disabled) + \since 4.3 + + Disables the item if \a disabled is true; otherwise enables the item. + + \sa setFlags() +*/ + +/*! + \fn bool QTreeWidgetItem::isDisabled() const + \since 4.3 + + Returns true if the item is disabled; otherwise returns false. + + \sa setFlags() +*/ + +/*! + \internal + + Constructs a tree model with a \a parent object and the given + number of \a columns. +*/ + +QTreeModel::QTreeModel(int columns, QTreeWidget *parent) + : QAbstractItemModel(parent), rootItem(new QTreeWidgetItem), + headerItem(new QTreeWidgetItem), skipPendingSort(false) +{ + rootItem->view = parent; + rootItem->itemFlags = Qt::ItemIsDropEnabled; + headerItem->view = parent; + setColumnCount(columns); +} + +/*! + \internal + +*/ + +QTreeModel::QTreeModel(QTreeModelPrivate &dd, QTreeWidget *parent) + : QAbstractItemModel(dd, parent), rootItem(new QTreeWidgetItem), + headerItem(new QTreeWidgetItem), skipPendingSort(false) +{ + rootItem->view = parent; + rootItem->itemFlags = Qt::ItemIsDropEnabled; + headerItem->view = parent; +} + +/*! + \internal + + Destroys this tree model. +*/ + +QTreeModel::~QTreeModel() +{ + clear(); + delete headerItem; + rootItem->view = 0; + delete rootItem; +} + +/*! + \internal + + Removes all items in the model. +*/ + +void QTreeModel::clear() +{ + SkipSorting skipSorting(this); + for (int i = 0; i < rootItem->childCount(); ++i) { + QTreeWidgetItem *item = rootItem->children.at(i); + item->par = 0; + item->view = 0; + delete item; + } + rootItem->children.clear(); + sortPendingTimer.stop(); + reset(); +} + +/*! + \internal + + Sets the number of \a columns in the tree model. +*/ + +void QTreeModel::setColumnCount(int columns) +{ + SkipSorting skipSorting(this); + if (columns < 0) + return; + if (!headerItem) { + headerItem = new QTreeWidgetItem(); + headerItem->view = view(); + } + int count = columnCount(); + if (count == columns) + return; + + if (columns < count) { + beginRemoveColumns(QModelIndex(), columns, count - 1); + headerItem->values.resize(columns); + endRemoveColumns(); + } else { + beginInsertColumns(QModelIndex(), count, columns - 1); + headerItem->values.resize(columns); + for (int i = count; i < columns; ++i) {// insert data without emitting the dataChanged signal + headerItem->values[i].append(QWidgetItemData(Qt::DisplayRole, QString::number(i + 1))); + headerItem->d->display.append(QString::number(i + 1)); + } + endInsertColumns(); + } +} + +/*! + \internal + + Returns the tree view item corresponding to the \a index given. + + \sa QModelIndex +*/ + +QTreeWidgetItem *QTreeModel::item(const QModelIndex &index) const +{ + if (!index.isValid()) + return 0; + return static_cast<QTreeWidgetItem*>(index.internalPointer()); +} + +/*! + \internal + + Returns the model index that refers to the + tree view \a item and \a column. +*/ + +QModelIndex QTreeModel::index(const QTreeWidgetItem *item, int column) const +{ + executePendingSort(); + + if (!item || (item == rootItem)) + return QModelIndex(); + const QTreeWidgetItem *par = item->parent(); + QTreeWidgetItem *itm = const_cast<QTreeWidgetItem*>(item); + if (!par) + par = rootItem; + int row; + int guess = item->d->rowGuess; + if (guess >= 0 + && par->children.count() > guess + && par->children.at(guess) == itm) { + row = guess; + } else { + row = par->children.lastIndexOf(itm); + itm->d->rowGuess = row; + } + return createIndex(row, column, itm); +} + +/*! + \internal + \reimp + + Returns the model index with the given \a row, + \a column and \a parent. +*/ + +QModelIndex QTreeModel::index(int row, int column, const QModelIndex &parent) const +{ + executePendingSort(); + + int c = columnCount(parent); + if (row < 0 || column < 0 || column >= c) + return QModelIndex(); + + QTreeWidgetItem *parentItem = parent.isValid() ? item(parent) : rootItem; + if (parentItem && row < parentItem->childCount()) { + QTreeWidgetItem *itm = parentItem->child(row); + if (itm) + return createIndex(row, column, itm); + return QModelIndex(); + } + + return QModelIndex(); +} + +/*! + \internal + \reimp + + Returns the parent model index of the index given as + the \a child. +*/ + +QModelIndex QTreeModel::parent(const QModelIndex &child) const +{ + SkipSorting skipSorting(this); //The reason we don't sort here is that this might be called from a valid QPersistentModelIndex + //We don't want it to become suddenly invalid + + if (!child.isValid()) + return QModelIndex(); + QTreeWidgetItem *itm = static_cast<QTreeWidgetItem *>(child.internalPointer()); + if (!itm || itm == rootItem) + return QModelIndex(); + QTreeWidgetItem *parent = itm->parent(); + return index(parent, 0); +} + +/*! + \internal + \reimp + + Returns the number of rows in the \a parent model index. +*/ + +int QTreeModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) + return rootItem->childCount(); + + QTreeWidgetItem *parentItem = item(parent); + if (parentItem) + return parentItem->childCount(); + return 0; +} + +/*! + \internal + \reimp + + Returns the number of columns in the item referred to by + the given \a index. +*/ + +int QTreeModel::columnCount(const QModelIndex &index) const +{ + Q_UNUSED(index); + if (!headerItem) + return 0; + return headerItem->columnCount(); +} + +bool QTreeModel::hasChildren(const QModelIndex &parent) const +{ + if (!parent.isValid()) + return (rootItem->childCount() > 0); + + QTreeWidgetItem *itm = item(parent); + if (!itm) + return false; + switch (itm->d->policy) { + case QTreeWidgetItem::ShowIndicator: + return true; + case QTreeWidgetItem::DontShowIndicator: + return false; + case QTreeWidgetItem::DontShowIndicatorWhenChildless: + return (itm->childCount() > 0); + } + return false; +} + +/*! + \internal + \reimp + + Returns the data corresponding to the given model \a index + and \a role. +*/ + +QVariant QTreeModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + QTreeWidgetItem *itm = item(index); + if (itm) + return itm->data(index.column(), role); + return QVariant(); +} + +/*! + \internal + \reimp + + Sets the data for the item specified by the \a index and \a role + to that referred to by the \a value. + + Returns true if successful; otherwise returns false. +*/ + +bool QTreeModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid()) + return false; + QTreeWidgetItem *itm = item(index); + if (itm) { + itm->setData(index.column(), role, value); + return true; + } + return false; +} + +QMap<int, QVariant> QTreeModel::itemData(const QModelIndex &index) const +{ + QMap<int, QVariant> roles; + QTreeWidgetItem *itm = item(index); + if (itm) { + int column = index.column(); + if (column < itm->values.count()) { + for (int i = 0; i < itm->values.at(column).count(); ++i) { + roles.insert(itm->values.at(column).at(i).role, + itm->values.at(column).at(i).value); + } + } + + // the two special cases + QVariant displayValue = itm->data(column, Qt::DisplayRole); + if (displayValue.isValid()) + roles.insert(Qt::DisplayRole, displayValue); + + QVariant checkValue = itm->data(column, Qt::CheckStateRole); + if (checkValue.isValid()) + roles.insert(Qt::CheckStateRole, checkValue); + } + return roles; +} + +/*! + \internal + \reimp +*/ +bool QTreeModel::insertRows(int row, int count, const QModelIndex &parent) +{ + SkipSorting skipSorting(this); + if (count < 1 || row < 0 || row > rowCount(parent) || parent.column() > 0) + return false; + + beginInsertRows(parent, row, row + count - 1); + QTreeWidgetItem *par = item(parent); + while (count > 0) { + QTreeWidgetItem *item = new QTreeWidgetItem(); + item->view = view(); + item->par = par; + if (par) + par->children.insert(row++, item); + else + rootItem->children.insert(row++, item); + --count; + } + endInsertRows(); + return true; +} + +/*! + \internal + \reimp +*/ +bool QTreeModel::insertColumns(int column, int count, const QModelIndex &parent) +{ + SkipSorting skipSorting(this); + if (count < 1 || column < 0 || column > columnCount(parent) || parent.column() > 0 || !headerItem) + return false; + + beginInsertColumns(parent, column, column + count - 1); + + int oldCount = columnCount(parent); + column = qBound(0, column, oldCount); + headerItem->values.resize(oldCount + count); + for (int i = oldCount; i < oldCount + count; ++i) { + headerItem->values[i].append(QWidgetItemData(Qt::DisplayRole, QString::number(i + 1))); + headerItem->d->display.append(QString::number(i + 1)); + } + + QStack<QTreeWidgetItem*> itemstack; + itemstack.push(0); + while (!itemstack.isEmpty()) { + QTreeWidgetItem *par = itemstack.pop(); + QList<QTreeWidgetItem*> children = par ? par->children : rootItem->children; + for (int row = 0; row < children.count(); ++row) { + QTreeWidgetItem *child = children.at(row); + if (child->children.count()) + itemstack.push(child); + child->values.insert(column, count, QVector<QWidgetItemData>()); + } + } + + endInsertColumns(); + return true; +} + +/*! + \internal + \reimp +*/ +bool QTreeModel::removeRows(int row, int count, const QModelIndex &parent) { + if (count < 1 || row < 0 || (row + count) > rowCount(parent)) + return false; + + beginRemoveRows(parent, row, row + count - 1); + + bool blockSignal = signalsBlocked(); + blockSignals(true); + + QTreeWidgetItem *itm = item(parent); + for (int i = row + count - 1; i >= row; --i) { + QTreeWidgetItem *child = itm ? itm->takeChild(i) : rootItem->children.takeAt(i); + Q_ASSERT(child); + child->view = 0; + delete child; + child = 0; + } + blockSignals(blockSignal); + + endRemoveRows(); + return true; +} + +/*! + \internal + \reimp + + Returns the header data corresponding to the given header \a section, + \a orientation and data \a role. +*/ + +QVariant QTreeModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation != Qt::Horizontal) + return QVariant(); + + if (headerItem) + return headerItem->data(section, role); + if (role == Qt::DisplayRole) + return QString::number(section + 1); + return QVariant(); +} + +/*! + \internal + \reimp + + Sets the header data for the item specified by the header \a section, + \a orientation and data \a role to the given \a value. + + Returns true if successful; otherwise returns false. +*/ + +bool QTreeModel::setHeaderData(int section, Qt::Orientation orientation, + const QVariant &value, int role) +{ + if (section < 0 || orientation != Qt::Horizontal || !headerItem || section >= columnCount()) + return false; + + headerItem->setData(section, role, value); + return true; +} + +/*! + \reimp + + Returns the flags for the item referred to the given \a index. + +*/ + +Qt::ItemFlags QTreeModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return rootItem->flags(); + QTreeWidgetItem *itm = item(index); + Q_ASSERT(itm); + return itm->flags(); +} + +/*! + \internal + + Sorts the entire tree in the model in the given \a order, + by the values in the given \a column. +*/ + +void QTreeModel::sort(int column, Qt::SortOrder order) +{ + SkipSorting skipSorting(this); + sortPendingTimer.stop(); + + if (column < 0 || column >= columnCount()) + return; + + //layoutAboutToBeChanged and layoutChanged will be called by sortChildren + rootItem->sortChildren(column, order, true); +} + +/*! + \internal +*/ +void QTreeModel::ensureSorted(int column, Qt::SortOrder order, + int start, int end, const QModelIndex &parent) +{ + if (isChanging()) + return; + + sortPendingTimer.stop(); + + if (column < 0 || column >= columnCount()) + return; + + SkipSorting skipSorting(this); + + QTreeWidgetItem *itm = item(parent); + if (!itm) + itm = rootItem; + QList<QTreeWidgetItem*> lst = itm->children; + + int count = end - start + 1; + QVector < QPair<QTreeWidgetItem*,int> > sorting(count); + for (int i = 0; i < count; ++i) { + sorting[i].first = lst.at(start + i); + sorting[i].second = start + i; + } + + LessThan compare = (order == Qt::AscendingOrder ? &itemLessThan : &itemGreaterThan); + qStableSort(sorting.begin(), sorting.end(), compare); + + QModelIndexList oldPersistentIndexes; + QModelIndexList newPersistentIndexes; + QList<QTreeWidgetItem*>::iterator lit = lst.begin(); + bool changed = false; + + for (int i = 0; i < count; ++i) { + int oldRow = sorting.at(i).second; + QTreeWidgetItem *item = lst.takeAt(oldRow); + lit = sortedInsertionIterator(lit, lst.end(), order, item); + int newRow = qMax(lit - lst.begin(), 0); + lit = lst.insert(lit, item); + if (newRow != oldRow) { + // we are going to change the persistent indexes, so we need to prepare + if (!changed) { // this will only happen once + changed = true; + emit layoutAboutToBeChanged(); // the selection model needs to know + oldPersistentIndexes = persistentIndexList(); + newPersistentIndexes = oldPersistentIndexes; + } + for (int j = i + 1; j < count; ++j) { + int otherRow = sorting.at(j).second; + if (oldRow < otherRow && newRow >= otherRow) + --sorting[j].second; + else if (oldRow > otherRow && newRow <= otherRow) + ++sorting[j].second; + } + for (int k = 0; k < newPersistentIndexes.count(); ++k) { + QModelIndex pi = newPersistentIndexes.at(k); + if (pi.parent() != parent) + continue; + int oldPersistentRow = pi.row(); + int newPersistentRow = oldPersistentRow; + if (oldPersistentRow == oldRow) + newPersistentRow = newRow; + else if (oldRow < oldPersistentRow && newRow >= oldPersistentRow) + newPersistentRow = oldPersistentRow - 1; + else if (oldRow > oldPersistentRow && newRow <= oldPersistentRow) + newPersistentRow = oldPersistentRow + 1; + if (newPersistentRow != oldPersistentRow) + newPersistentIndexes[k] = createIndex(newPersistentRow, + pi.column(), pi.internalPointer()); + } + } + } + + if (changed) { + itm->children = lst; + changePersistentIndexList(oldPersistentIndexes, newPersistentIndexes); + emit layoutChanged(); + } +} + +/*! + \internal + + Returns true if the value of the \a left item is + less than the value of the \a right item. + + Used by the sorting functions. +*/ + +bool QTreeModel::itemLessThan(const QPair<QTreeWidgetItem*,int> &left, + const QPair<QTreeWidgetItem*,int> &right) +{ + return *(left.first) < *(right.first); +} + +/*! + \internal + + Returns true if the value of the \a left item is + greater than the value of the \a right item. + + Used by the sorting functions. +*/ + +bool QTreeModel::itemGreaterThan(const QPair<QTreeWidgetItem*,int> &left, + const QPair<QTreeWidgetItem*,int> &right) +{ + return *(right.first) < *(left.first); +} + +/*! + \internal +*/ +QList<QTreeWidgetItem*>::iterator QTreeModel::sortedInsertionIterator( + const QList<QTreeWidgetItem*>::iterator &begin, + const QList<QTreeWidgetItem*>::iterator &end, + Qt::SortOrder order, QTreeWidgetItem *item) +{ + if (order == Qt::AscendingOrder) + return qLowerBound(begin, end, item, QTreeModelLessThan()); + return qLowerBound(begin, end, item, QTreeModelGreaterThan()); +} + +QStringList QTreeModel::mimeTypes() const +{ + return view()->mimeTypes(); +} + +QMimeData *QTreeModel::internalMimeData() const +{ + return QAbstractItemModel::mimeData(cachedIndexes); +} + +QMimeData *QTreeModel::mimeData(const QModelIndexList &indexes) const +{ + QList<QTreeWidgetItem*> items; + for (int i = 0; i < indexes.count(); ++i) { + if (indexes.at(i).column() == 0) // only one item per row + items << item(indexes.at(i)); + } + + // cachedIndexes is a little hack to avoid copying from QModelIndexList to + // QList<QTreeWidgetItem*> and back again in the view + cachedIndexes = indexes; + QMimeData *mimeData = view()->mimeData(items); + cachedIndexes.clear(); + return mimeData; +} + +bool QTreeModel::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent) +{ + if (row == -1 && column == -1) + row = rowCount(parent); // append + return view()->dropMimeData(item(parent), row, data, action); +} + +Qt::DropActions QTreeModel::supportedDropActions() const +{ + return view()->supportedDropActions(); +} + +void QTreeModel::itemChanged(QTreeWidgetItem *item) +{ + SkipSorting skipSorting(this); //this is kind of wrong, but not doing this would kill performence + QModelIndex left = index(item, 0); + QModelIndex right = index(item, item->columnCount() - 1); + emit dataChanged(left, right); +} + +bool QTreeModel::isChanging() const +{ + Q_D(const QTreeModel); + return !d->changes.isEmpty(); +} + +/*! + \internal + Emits the dataChanged() signal for the given \a item. + if column is -1 then all columns have changed +*/ + +void QTreeModel::emitDataChanged(QTreeWidgetItem *item, int column) +{ + if (signalsBlocked()) + return; + + if (headerItem == item && column < item->columnCount()) { + if (column == -1) + emit headerDataChanged(Qt::Horizontal, 0, columnCount() - 1); + else + emit headerDataChanged(Qt::Horizontal, column, column); + return; + } + + SkipSorting skipSorting(this); //This is a little bit wrong, but not doing it would kill performence + + QModelIndex bottomRight, topLeft; + if (column == -1) { + topLeft = index(item, 0); + bottomRight = createIndex(topLeft.row(), columnCount() - 1, item); + } else { + topLeft = index(item, column); + bottomRight = topLeft; + } + emit dataChanged(topLeft, bottomRight); +} + +void QTreeModel::beginInsertItems(QTreeWidgetItem *parent, int row, int count) +{ + QModelIndex par = index(parent, 0); + beginInsertRows(par, row, row + count - 1); +} + +void QTreeModel::endInsertItems() +{ + endInsertRows(); +} + +void QTreeModel::beginRemoveItems(QTreeWidgetItem *parent, int row, int count) +{ + Q_ASSERT(row >= 0); + Q_ASSERT(count > 0); + beginRemoveRows(index(parent, 0), row, row + count - 1); + if (!parent) + parent = rootItem; + // now update the iterators + for (int i = 0; i < iterators.count(); ++i) { + for (int j = 0; j < count; j++) { + QTreeWidgetItem *c = parent->child(row + j); + iterators[i]->d_func()->ensureValidIterator(c); + } + } +} + +void QTreeModel::endRemoveItems() +{ + endRemoveRows(); +} + +void QTreeModel::sortItems(QList<QTreeWidgetItem*> *items, int column, Qt::SortOrder order) +{ + // see QTreeViewItem::operator< + Q_UNUSED(column); + if (isChanging()) + return; + + // store the original order of indexes + QVector< QPair<QTreeWidgetItem*,int> > sorting(items->count()); + for (int i = 0; i < sorting.count(); ++i) { + sorting[i].first = items->at(i); + sorting[i].second = i; + } + + // do the sorting + LessThan compare = (order == Qt::AscendingOrder ? &itemLessThan : &itemGreaterThan); + qStableSort(sorting.begin(), sorting.end(), compare); + + QModelIndexList fromList; + QModelIndexList toList; + int colCount = columnCount(); + for (int r = 0; r < sorting.count(); ++r) { + int oldRow = sorting.at(r).second; + if (oldRow == r) + continue; + QTreeWidgetItem *item = sorting.at(r).first; + items->replace(r, item); + for (int c = 0; c < colCount; ++c) { + QModelIndex from = createIndex(oldRow, c, item); + if (static_cast<QAbstractItemModelPrivate *>(d_ptr)->persistent.indexes.contains(from)) { + QModelIndex to = createIndex(r, c, item); + fromList << from; + toList << to; + } + } + } + changePersistentIndexList(fromList, toList); +} + +void QTreeModel::timerEvent(QTimerEvent *ev) +{ + if (ev->timerId() == sortPendingTimer.timerId()) { + executePendingSort(); + } else { + QAbstractItemModel::timerEvent(ev); + } +} + +/*! + \class QTreeWidgetItem + + \brief The QTreeWidgetItem class provides an item for use with the + QTreeWidget convenience class. + + \ingroup model-view + + Tree widget items are used to hold rows of information for tree widgets. + Rows usually contain several columns of data, each of which can contain + a text label and an icon. + + The QTreeWidgetItem class is a convenience class that replaces the + QListViewItem class in Qt 3. It provides an item for use with + the QTreeWidget class. + + Items are usually constructed with a parent that is either a QTreeWidget + (for top-level items) or a QTreeWidgetItem (for items on lower levels of + the tree). For example, the following code constructs a top-level item + to represent cities of the world, and adds a entry for Oslo as a child + item: + + \snippet doc/src/snippets/qtreewidget-using/mainwindow.cpp 3 + + Items can be added in a particular order by specifying the item they + follow when they are constructed: + + \snippet doc/src/snippets/qtreewidget-using/mainwindow.cpp 5 + + Each column in an item can have its own background brush which is set with + the setBackground() function. The current background brush can be + found with background(). + The text label for each column can be rendered with its own font and brush. + These are specified with the setFont() and setForeground() functions, + and read with font() and foreground(). + + The main difference between top-level items and those in lower levels of + the tree is that a top-level item has no parent(). This information + can be used to tell the difference between items, and is useful to know + when inserting and removing items from the tree. + Children of an item can be removed with takeChild() and inserted at a + given index in the list of children with the insertChild() function. + + By default, items are enabled, selectable, checkable, and can be the source + of a drag and drop operation. + Each item's flags can be changed by calling setFlags() with the appropriate + value (see \l{Qt::ItemFlags}). Checkable items can be checked and unchecked + with the setCheckState() function. The corresponding checkState() function + indicates whether the item is currently checked. + + \section1 Subclassing + + When subclassing QTreeWidgetItem to provide custom items, it is possible to + define new types for them so that they can be distinguished from standard + items. The constructors for subclasses that require this feature need to + call the base class constructor with a new type value equal to or greater + than \l UserType. + + \sa QTreeWidget, QTreeWidgetItemIterator, {Model/View Programming}, + QListWidgetItem, QTableWidgetItem +*/ + +/*! + \enum QTreeWidgetItem::ItemType + + This enum describes the types that are used to describe tree widget items. + + \value Type The default type for tree widget items. + \value UserType The minimum value for custom types. Values below UserType are + reserved by Qt. + + You can define new user types in QTreeWidgetItem subclasses to ensure that + custom items are treated specially; for example, when items are sorted. + + \sa type() +*/ + +/*! + \fn int QTreeWidgetItem::type() const + + Returns the type passed to the QTreeWidgetItem constructor. +*/ + +/*! + \fn void QTreeWidgetItem::sortChildren(int column, Qt::SortOrder order) + \since 4.2 + + Sorts the children of the item using the given \a order, + by the values in the given \a column. + + \note This function does nothing if the item is not associated with a + QTreeWidget. +*/ + +/*! + \fn QTreeWidget *QTreeWidgetItem::treeWidget() const + + Returns the tree widget that contains the item. +*/ + +/*! + \fn void QTreeWidgetItem::setSelected(bool select) + \since 4.2 + + Sets the selected state of the item to \a select. + + \sa isSelected() + +*/ + +/*! + \fn bool QTreeWidgetItem::isSelected() const + \since 4.2 + + Returns true if the item is selected, otherwise returns false. + + \sa setSelected() +*/ + +/*! + \fn void QTreeWidgetItem::setHidden(bool hide) + \since 4.2 + + Hides the item if \a hide is true, otherwise shows the item. + + \sa isHidden() +*/ + +/*! + \fn bool QTreeWidgetItem::isHidden() const + \since 4.2 + + Returns true if the item is hidden, otherwise returns false. + + \sa setHidden() +*/ + +/*! + \fn void QTreeWidgetItem::setExpanded(bool expand) + \since 4.2 + + Expands the item if \a expand is true, otherwise collapses the item. + \warning The QTreeWidgetItem must be added to the QTreeWidget before calling this function. + + \sa isExpanded() +*/ + +/*! + \fn bool QTreeWidgetItem::isExpanded() const + \since 4.2 + + Returns true if the item is expanded, otherwise returns false. + + \sa setExpanded() +*/ + +/*! + \fn void QTreeWidgetItem::setFirstColumnSpanned(bool span) + \since 4.3 + + Sets the first section to span all columns if \a span is true; + otherwise all item sections are shown. + + \sa isFirstColumnSpanned() +*/ + +/*! + \fn bool QTreeWidgetItem::isFirstColumnSpanned() const + \since 4.3 + + Returns true if the item is spanning all the columns in a row; otherwise returns false. + + \sa setFirstColumnSpanned() +*/ + +/*! + \fn QString QTreeWidgetItem::text(int column) const + + Returns the text in the specified \a column. + + \sa setText() +*/ + +/*! + \fn void QTreeWidgetItem::setText(int column, const QString &text) + + Sets the text to be displayed in the given \a column to the given \a text. + + \sa text() setFont() setForeground() +*/ + +/*! + \fn QIcon QTreeWidgetItem::icon(int column) const + + Returns the icon that is displayed in the specified \a column. + + \sa setIcon(), {QAbstractItemView::iconSize}{iconSize} +*/ + +/*! + \fn void QTreeWidgetItem::setIcon(int column, const QIcon &icon) + + Sets the icon to be displayed in the given \a column to \a icon. + + \sa icon(), setText(), {QAbstractItemView::iconSize}{iconSize} +*/ + +/*! + \fn QString QTreeWidgetItem::statusTip(int column) const + + Returns the status tip for the contents of the given \a column. + + \sa setStatusTip() +*/ + +/*! + \fn void QTreeWidgetItem::setStatusTip(int column, const QString &statusTip) + + Sets the status tip for the given \a column to the given \a statusTip. + QTreeWidget mouse tracking needs to be enabled for this feature to work. + + \sa statusTip() setToolTip() setWhatsThis() +*/ + +/*! + \fn QString QTreeWidgetItem::toolTip(int column) const + + Returns the tool tip for the given \a column. + + \sa setToolTip() +*/ + +/*! + \fn void QTreeWidgetItem::setToolTip(int column, const QString &toolTip) + + Sets the tooltip for the given \a column to \a toolTip. + + \sa toolTip() setStatusTip() setWhatsThis() +*/ + +/*! + \fn QString QTreeWidgetItem::whatsThis(int column) const + + Returns the "What's This?" help for the contents of the given \a column. + + \sa setWhatsThis() +*/ + +/*! + \fn void QTreeWidgetItem::setWhatsThis(int column, const QString &whatsThis) + + Sets the "What's This?" help for the given \a column to \a whatsThis. + + \sa whatsThis() setStatusTip() setToolTip() +*/ + +/*! + \fn QFont QTreeWidgetItem::font(int column) const + + Returns the font used to render the text in the specified \a column. + + \sa setFont() +*/ + +/*! + \fn void QTreeWidgetItem::setFont(int column, const QFont &font) + + Sets the font used to display the text in the given \a column to the given + \a font. + + \sa font() setText() setForeground() +*/ + +/*! + \fn QColor QTreeWidgetItem::backgroundColor(int column) const + \obsolete + + This function is deprecated. Use background() instead. +*/ + +/*! + \fn void QTreeWidgetItem::setBackgroundColor(int column, const QColor &color) + \obsolete + + This function is deprecated. Use setBackground() instead. +*/ + +/*! + \fn QBrush QTreeWidgetItem::background(int column) const + \since 4.2 + + Returns the brush used to render the background of the specified \a column. + + \sa foreground() +*/ + +/*! + \fn void QTreeWidgetItem::setBackground(int column, const QBrush &brush) + \since 4.2 + + Sets the background brush of the label in the given \a column to the + specified \a brush. + + \sa setForeground() +*/ + +/*! + \fn QColor QTreeWidgetItem::textColor(int column) const + \obsolete + + This function is deprecated. Use foreground() instead. +*/ + +/*! + \fn void QTreeWidgetItem::setTextColor(int column, const QColor &color) + \obsolete + + This function is deprecated. Use setForeground() instead. +*/ + +/*! + \fn QBrush QTreeWidgetItem::foreground(int column) const + \since 4.2 + + Returns the brush used to render the foreground (e.g. text) of the + specified \a column. + + \sa background() +*/ + +/*! + \fn void QTreeWidgetItem::setForeground(int column, const QBrush &brush) + \since 4.2 + + Sets the foreground brush of the label in the given \a column to the + specified \a brush. + + \sa setBackground() +*/ + +/*! + \fn Qt::CheckState QTreeWidgetItem::checkState(int column) const + + Returns the check state of the label in the given \a column. + + \sa Qt::CheckState +*/ + +/*! + \fn void QTreeWidgetItem::setCheckState(int column, Qt::CheckState state) + + Sets the item in the given \a column check state to be \a state. + + \sa checkState() +*/ + +/*! + \fn QSize QTreeWidgetItem::sizeHint(int column) const + \since 4.1 + + Returns the size hint set for the tree item in the given + \a column (see \l{QSize}). +*/ + +/*! + \fn void QTreeWidgetItem::setSizeHint(int column, const QSize &size) + \since 4.1 + + Sets the size hint for the tree item in the given \a column to be \a size. + If no size hint is set, the item delegate will compute the size hint based + on the item data. +*/ + +/*! + \fn QTreeWidgetItem *QTreeWidgetItem::parent() const + + Returns the item's parent. + + \sa child() +*/ + +/*! + \fn QTreeWidgetItem *QTreeWidgetItem::child(int index) const + + Returns the item at the given \a index in the list of the item's children. + + \sa parent() +*/ + +/*! + \fn int QTreeWidgetItem::childCount() const + + Returns the number of child items. +*/ + +/*! + \fn int QTreeWidgetItem::columnCount() const + + Returns the number of columns in the item. +*/ + +/*! + \fn int QTreeWidgetItem::textAlignment(int column) const + + Returns the text alignment for the label in the given \a column + (see \l{Qt::AlignmentFlag}). +*/ + +/*! + \fn void QTreeWidgetItem::setTextAlignment(int column, int alignment) + + Sets the text alignment for the label in the given \a column to + the \a alignment specified (see \l{Qt::AlignmentFlag}). +*/ + +/*! + \fn int QTreeWidgetItem::indexOfChild(QTreeWidgetItem *child) const + + Returns the index of the given \a child in the item's list of children. +*/ + +/*! + Constructs a tree widget item of the specified \a type. The item + must be inserted into a tree widget. + + \sa type() +*/ +QTreeWidgetItem::QTreeWidgetItem(int type) + : rtti(type), view(0), d(new QTreeWidgetItemPrivate(this)), par(0), + itemFlags(Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled) +{ +} + + +/*! + Constructs a tree widget item of the specified \a type. The item + must be inserted into a tree widget. + The given list of \a strings will be set as the item text for each + column in the item. + + \sa type() +*/ +QTreeWidgetItem::QTreeWidgetItem(const QStringList &strings, int type) + : rtti(type), view(0), d(new QTreeWidgetItemPrivate(this)), par(0), + itemFlags(Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled) +{ + for (int i = 0; i < strings.count(); ++i) + setText(i, strings.at(i)); +} + +/*! + \fn QTreeWidgetItem::QTreeWidgetItem(QTreeWidget *parent, int type) + + Constructs a tree widget item of the specified \a type and appends it + to the items in the given \a parent. + + \sa type() +*/ + +QTreeWidgetItem::QTreeWidgetItem(QTreeWidget *view, int type) + : rtti(type), view(0), d(new QTreeWidgetItemPrivate(this)), par(0), + itemFlags(Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled) +{ + if (view && view->model()) { + QTreeModel *model = qobject_cast<QTreeModel*>(view->model()); + model->rootItem->addChild(this); + values.reserve(model->headerItem->columnCount()); + } +} + +/*! + \fn QTreeWidgetItem::QTreeWidgetItem(QTreeWidget *parent, const QStringList &strings, int type) + + Constructs a tree widget item of the specified \a type and appends it + to the items in the given \a parent. The given list of \a strings will be set as + the item text for each column in the item. + + \sa type() +*/ + +QTreeWidgetItem::QTreeWidgetItem(QTreeWidget *view, const QStringList &strings, int type) + : rtti(type), view(0), d(new QTreeWidgetItemPrivate(this)), par(0), + itemFlags(Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled) +{ + for (int i = 0; i < strings.count(); ++i) + setText(i, strings.at(i)); + if (view && view->model()) { + QTreeModel *model = qobject_cast<QTreeModel*>(view->model()); + model->rootItem->addChild(this); + values.reserve(model->headerItem->columnCount()); + } +} + +/*! + \fn QTreeWidgetItem::QTreeWidgetItem(QTreeWidget *parent, QTreeWidgetItem *preceding, int type) + + Constructs a tree widget item of the specified \a type and inserts it into + the given \a parent after the \a preceding item. + + \sa type() +*/ +QTreeWidgetItem::QTreeWidgetItem(QTreeWidget *view, QTreeWidgetItem *after, int type) + : rtti(type), view(0), d(new QTreeWidgetItemPrivate(this)), par(0), + itemFlags(Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled) +{ + if (view) { + QTreeModel *model = qobject_cast<QTreeModel*>(view->model()); + if (model) { + int i = model->rootItem->children.indexOf(after) + 1; + model->rootItem->insertChild(i, this); + values.reserve(model->headerItem->columnCount()); + } + } +} + +/*! + Constructs a tree widget item and append it to the given \a parent. + + \sa type() +*/ +QTreeWidgetItem::QTreeWidgetItem(QTreeWidgetItem *parent, int type) + : rtti(type), view(0), d(new QTreeWidgetItemPrivate(this)), par(0), + itemFlags(Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled) +{ + if (parent) + parent->addChild(this); +} + +/*! + Constructs a tree widget item and append it to the given \a parent. + The given list of \a strings will be set as the item text for each column in the item. + + \sa type() +*/ +QTreeWidgetItem::QTreeWidgetItem(QTreeWidgetItem *parent, const QStringList &strings, int type) + : rtti(type), view(0), d(new QTreeWidgetItemPrivate(this)), par(0), + itemFlags(Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled) +{ + for (int i = 0; i < strings.count(); ++i) + setText(i, strings.at(i)); + if (parent) + parent->addChild(this); +} + +/*! + \fn QTreeWidgetItem::QTreeWidgetItem(QTreeWidgetItem *parent, QTreeWidgetItem *preceding, int type) + + Constructs a tree widget item of the specified \a type that is inserted + into the \a parent after the \a preceding child item. + + \sa type() +*/ +QTreeWidgetItem::QTreeWidgetItem(QTreeWidgetItem *parent, QTreeWidgetItem *after, int type) + : rtti(type), view(0), d(new QTreeWidgetItemPrivate(this)), par(0), + itemFlags(Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled) +{ + if (parent) { + int i = parent->children.indexOf(after) + 1; + parent->insertChild(i, this); + } +} + +/*! + Destroys this tree widget item. +*/ + +QTreeWidgetItem::~QTreeWidgetItem() +{ + QTreeModel *model = (view ? qobject_cast<QTreeModel*>(view->model()) : 0); + bool wasSkipSort = false; + if (model) { + wasSkipSort = model->skipPendingSort; + model->skipPendingSort = true; + } + if (par) { + int i = par->children.indexOf(this); + if (i >= 0) { + if (model) model->beginRemoveItems(par, i, 1); + // users _could_ do changes when connected to rowsAboutToBeRemoved, + // so we check again to make sure 'i' is valid + if (!par->children.isEmpty() && par->children.at(i) == this) + par->children.takeAt(i); + if (model) model->endRemoveItems(); + } + } else if (model) { + if (this == model->headerItem) { + model->headerItem = 0; + } else { + int i = model->rootItem->children.indexOf(this); + if (i >= 0) { + model->beginRemoveItems(0, i, 1); + // users _could_ do changes when connected to rowsAboutToBeRemoved, + // so we check again to make sure 'i' is valid + if (!model->rootItem->children.isEmpty() && model->rootItem->children.at(i) == this) + model->rootItem->children.takeAt(i); + model->endRemoveItems(); + } + } + } + // at this point the persistent indexes for the children should also be invalidated + // since we invalidated the parent + for (int i = 0; i < children.count(); ++i) { + QTreeWidgetItem *child = children.at(i); + // make sure the child does not try to remove itself from our children list + child->par = 0; + // make sure the child does not try to remove itself from the top level list + child->view = 0; + delete child; + } + + children.clear(); + delete d; + if (model) { + model->skipPendingSort = wasSkipSort; + } +} + +/*! + Creates a deep copy of the item and of its children. +*/ +QTreeWidgetItem *QTreeWidgetItem::clone() const +{ + QTreeWidgetItem *copy = 0; + + QStack<const QTreeWidgetItem*> stack; + QStack<QTreeWidgetItem*> parentStack; + stack.push(this); + parentStack.push(0); + + QTreeWidgetItem *root = 0; + const QTreeWidgetItem *item = 0; + QTreeWidgetItem *parent = 0; + while (!stack.isEmpty()) { + // get current item, and copied parent + item = stack.pop(); + parent = parentStack.pop(); + + // copy item + copy = new QTreeWidgetItem(*item); + if (!root) + root = copy; + + // set parent and add to parents children list + if (parent) { + copy->par = parent; + parent->children.insert(0, copy); + } + + for (int i = 0; i < item->childCount(); ++i) { + stack.push(item->child(i)); + parentStack.push(copy); + } + } + return root; +} + +/*! + Sets the item indicator \a policy. This policy decides when the + tree branch expand/collapse indicator is shown. + The default value is ShowForChildren. + + \sa childIndicatorPolicy() +*/ +void QTreeWidgetItem::setChildIndicatorPolicy(QTreeWidgetItem::ChildIndicatorPolicy policy) +{ + if (d->policy == policy) + return; + d->policy = policy; + + if (!view) + return; + + view->viewport()->update( view->d_func()->itemDecorationRect(view->d_func()->index(this))); +} + +/*! + Returns the item indicator policy. This policy decides when the + tree branch expand/collapse indicator is shown. + + \sa setChildIndicatorPolicy() +*/ +QTreeWidgetItem::ChildIndicatorPolicy QTreeWidgetItem::childIndicatorPolicy() const +{ + return d->policy; +} + +/*! + \fn void QTreeWidgetItem::setFlags(Qt::ItemFlags flags) + + Sets the flags for the item to the given \a flags. These determine whether + the item can be selected or modified. This is often used to disable an item. + + \sa flags() +*/ +void QTreeWidgetItem::setFlags(Qt::ItemFlags flags) +{ + const bool enable = (flags & Qt::ItemIsEnabled); + const bool changedState = bool(itemFlags & Qt::ItemIsEnabled) != enable; + const bool changedExplicit = d->disabled != !enable; + + d->disabled = !enable; + + if (enable && par && !(par->itemFlags & Qt::ItemIsEnabled)) // inherit from parent + itemFlags = flags & ~Qt::ItemIsEnabled; + else // this item is explicitly disabled or has no parent + itemFlags = flags; + + if (changedState && changedExplicit) { // if the propagate the change to the children + QStack<QTreeWidgetItem*> parents; + parents.push(this); + while (!parents.isEmpty()) { + QTreeWidgetItem *parent = parents.pop(); + for (int i = 0; i < parent->children.count(); ++i) { + QTreeWidgetItem *child = parent->children.at(i); + if (!child->d->disabled) { // if not explicitly disabled + parents.push(child); + if (enable) + child->itemFlags = child->itemFlags | Qt::ItemIsEnabled; + else + child->itemFlags = child->itemFlags & ~Qt::ItemIsEnabled; + child->itemChanged(); // ### we may want to optimize this + } + } + } + } + itemChanged(); +} + +void QTreeWidgetItemPrivate::propagateDisabled(QTreeWidgetItem *item) +{ + Q_ASSERT(item); + const bool enable = item->par ? (item->par->itemFlags.testFlag(Qt::ItemIsEnabled)) : true; + + QStack<QTreeWidgetItem*> parents; + parents.push(item); + while (!parents.isEmpty()) { + QTreeWidgetItem *parent = parents.pop(); + if (!parent->d->disabled) { // if not explicitly disabled + Qt::ItemFlags oldFlags = parent->itemFlags; + if (enable) + parent->itemFlags = parent->itemFlags | Qt::ItemIsEnabled; + else + parent->itemFlags = parent->itemFlags & ~Qt::ItemIsEnabled; + if (parent->itemFlags != oldFlags) + parent->itemChanged(); + } + + for (int i = 0; i < parent->children.count(); ++i) { + QTreeWidgetItem *child = parent->children.at(i); + parents.push(child); + } + } +} +/*! + \fn Qt::ItemFlags QTreeWidgetItem::flags() const + + Returns the flags used to describe the item. These determine whether + the item can be checked, edited, and selected. + + The default value for flags is + Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled. + If the item was constructed with a parent, flags will in addition contain Qt::ItemIsDropEnabled. + + \sa setFlags() +*/ +Qt::ItemFlags QTreeWidgetItem::flags() const +{ + return itemFlags; +} + +/*! + Sets the value for the item's \a column and \a role to the given + \a value. + + The \a role describes the type of data specified by \a value, and is defined by + the Qt::ItemDataRole enum. +*/ +void QTreeWidgetItem::setData(int column, int role, const QVariant &value) +{ + if (column < 0) + return; + + QTreeModel *model = (view ? qobject_cast<QTreeModel*>(view->model()) : 0); + switch (role) { + case Qt::EditRole: + case Qt::DisplayRole: { + if (values.count() <= column) { + if (model && this == model->headerItem) + model->setColumnCount(column + 1); + else + values.resize(column + 1); + } + if (d->display.count() <= column) { + for (int i = d->display.count() - 1; i < column - 1; ++i) + d->display.append(QVariant()); + d->display.append(value); + } else if (d->display[column] != value) { + d->display[column] = value; + } else { + return; // value is unchanged + } + } break; + case Qt::CheckStateRole: + if (itemFlags & Qt::ItemIsTristate) { + for (int i = 0; i < children.count(); ++i) { + QTreeWidgetItem *child = children.at(i); + if (child->data(column, role).isValid()) {// has a CheckState + Qt::ItemFlags f = itemFlags; // a little hack to avoid multiple dataChanged signals + itemFlags &= ~Qt::ItemIsTristate; + child->setData(column, role, value); + itemFlags = f; + } + } + } + // Don't break, but fall through + default: + if (column < values.count()) { + bool found = false; + QVector<QWidgetItemData> column_values = values.at(column); + for (int i = 0; i < column_values.count(); ++i) { + if (column_values.at(i).role == role) { + if (column_values.at(i).value == value) + return; // value is unchanged + values[column][i].value = value; + found = true; + break; + } + } + if (!found) + values[column].append(QWidgetItemData(role, value)); + } else { + if (model && this == model->headerItem) + model->setColumnCount(column + 1); + else + values.resize(column + 1); + values[column].append(QWidgetItemData(role, value)); + } + } + + if (model) { + model->emitDataChanged(this, column); + if (role == Qt::CheckStateRole) { + QTreeWidgetItem *p; + for (p = par; p && (p->itemFlags & Qt::ItemIsTristate); p = p->par) + model->emitDataChanged(p, column); + } + } +} + +/*! + Returns the value for the item's \a column and \a role. +*/ +QVariant QTreeWidgetItem::data(int column, int role) const +{ + switch (role) { + case Qt::EditRole: + case Qt::DisplayRole: + if (column >= 0 && column < d->display.count()) + return d->display.at(column); + break; + case Qt::CheckStateRole: + // special case for check state in tristate + if (children.count() && (itemFlags & Qt::ItemIsTristate)) + return childrenCheckState(column); + default: + if (column >= 0 && column < values.size()) { + const QVector<QWidgetItemData> &column_values = values.at(column); + for (int i = 0; i < column_values.count(); ++i) + if (column_values.at(i).role == role) + return column_values.at(i).value; + } + } + return QVariant(); +} + +/*! + Returns true if the text in the item is less than the text in the + \a other item, otherwise returns false. +*/ + +bool QTreeWidgetItem::operator<(const QTreeWidgetItem &other) const +{ + int column = view ? view->sortColumn() : 0; + return text(column) < other.text(column); +} + +#ifndef QT_NO_DATASTREAM + +/*! + Reads the item from stream \a in. This only reads data into a single item. + + \sa write() +*/ +void QTreeWidgetItem::read(QDataStream &in) +{ + // convert from streams written before we introduced display (4.2.0) + if (in.version() < QDataStream::Qt_4_2) { + d->display.clear(); + in >> values; + // move the display value over to the display string list + for (int column = 0; column < values.count(); ++column) { + d->display << QVariant(); + for (int i = 0; i < values.at(column).count(); ++i) { + if (values.at(column).at(i).role == Qt::DisplayRole) { + d->display[column] = values.at(column).at(i).value; + values[column].remove(i--); + } + } + } + } else { + in >> values >> d->display; + } +} + +/*! + Writes the item to stream \a out. This only writes data from one single item. + + \sa read() +*/ +void QTreeWidgetItem::write(QDataStream &out) const +{ + out << values << d->display; +} + +/*! + \since 4.1 + + Constructs a copy of \a other. Note that type() and treeWidget() + are not copied. + + This function is useful when reimplementing clone(). + + \sa data(), flags() +*/ +QTreeWidgetItem::QTreeWidgetItem(const QTreeWidgetItem &other) + : rtti(Type), values(other.values), view(0), + d(new QTreeWidgetItemPrivate(this)), par(0), + itemFlags(other.itemFlags) +{ + d->display = other.d->display; +} + +/*! + Assigns \a other's data and flags to this item. Note that type() + and treeWidget() are not copied. + + This function is useful when reimplementing clone(). + + \sa data(), flags() +*/ +QTreeWidgetItem &QTreeWidgetItem::operator=(const QTreeWidgetItem &other) +{ + values = other.values; + d->display = other.d->display; + d->policy = other.d->policy; + itemFlags = other.itemFlags; + return *this; +} + +#endif // QT_NO_DATASTREAM + +/*! + Appends the \a child item to the list of children. + + \sa insertChild() takeChild() +*/ +void QTreeWidgetItem::addChild(QTreeWidgetItem *child) +{ + if (child) { + insertChild(children.count(), child); + child->d->rowGuess = children.count() - 1; + } +} + +/*! + Inserts the \a child item at \a index in the list of children. + + If the child has already been inserted somewhere else it wont be inserted again. +*/ +void QTreeWidgetItem::insertChild(int index, QTreeWidgetItem *child) +{ + if (index < 0 || index > children.count() || child == 0 || child->view != 0 || child->par != 0) + return; + + if (QTreeModel *model = (view ? qobject_cast<QTreeModel*>(view->model()) : 0)) { + const bool wasSkipSort = model->skipPendingSort; + model->skipPendingSort = true; + if (model->rootItem == this) + child->par = 0; + else + child->par = this; + if (view->isSortingEnabled()) { + // do a delayed sort instead + if (!model->sortPendingTimer.isActive()) + model->sortPendingTimer.start(0, model); + } + model->beginInsertItems(this, index, 1); + int cols = model->columnCount(); + QStack<QTreeWidgetItem*> stack; + stack.push(child); + while (!stack.isEmpty()) { + QTreeWidgetItem *i = stack.pop(); + i->view = view; + i->values.reserve(cols); + for (int c = 0; c < i->children.count(); ++c) + stack.push(i->children.at(c)); + } + children.insert(index, child); + model->endInsertItems(); + model->skipPendingSort = wasSkipSort; + } else { + child->par = this; + children.insert(index, child); + } + if (child->par) + d->propagateDisabled(child); +} + +/*! + Removes the given item indicated by \a child. + The removed item will not be deleted. +*/ +void QTreeWidgetItem::removeChild(QTreeWidgetItem *child) +{ + (void)takeChild(children.indexOf(child)); +} + +/*! + Removes the item at \a index and returns it, otherwise return 0. +*/ +QTreeWidgetItem *QTreeWidgetItem::takeChild(int index) +{ + // we move this outside the check of the index to allow executing + // pending sorts from inline functions, using this function (hack) + QTreeModel *model = (view ? qobject_cast<QTreeModel*>(view->model()) : 0); + if (model) { + // This will trigger a layoutChanged signal, thus we might want to optimize + // this function by not emitting the rowsRemoved signal etc to the view. + // On the other hand we also need to make sure that the selectionmodel + // is updated in case we take an item that is selected. + model->skipPendingSort = false; + model->executePendingSort(); + } + if (index >= 0 && index < children.count()) { + if (model) model->beginRemoveItems(this, index, 1); + QTreeWidgetItem *item = children.takeAt(index); + item->par = 0; + QStack<QTreeWidgetItem*> stack; + stack.push(item); + while (!stack.isEmpty()) { + QTreeWidgetItem *i = stack.pop(); + i->view = 0; + for (int c = 0; c < i->children.count(); ++c) + stack.push(i->children.at(c)); + } + d->propagateDisabled(item); + if (model) model->endRemoveRows(); + return item; + } + return 0; +} + +/*! + \since 4.1 + + Appends the given list of \a children to the item. + + \sa insertChildren() takeChildren() +*/ +void QTreeWidgetItem::addChildren(const QList<QTreeWidgetItem*> &children) +{ + insertChildren(this->children.count(), children); +} + +/*! + \since 4.1 + + Inserts the given list of \a children into the list of the item children at \a index . + + Children that have already been inserted somewhere else wont be inserted. +*/ +void QTreeWidgetItem::insertChildren(int index, const QList<QTreeWidgetItem*> &children) +{ + if (view && view->isSortingEnabled()) { + for (int n = 0; n < children.count(); ++n) + insertChild(index, children.at(n)); + return; + } + QTreeModel *model = (view ? qobject_cast<QTreeModel*>(view->model()) : 0); + QStack<QTreeWidgetItem*> stack; + QList<QTreeWidgetItem*> itemsToInsert; + for (int n = 0; n < children.count(); ++n) { + QTreeWidgetItem *child = children.at(n); + if (child->view || child->par) + continue; + itemsToInsert.append(child); + if (view && model) { + if (child->childCount() == 0) + child->view = view; + else + stack.push(child); + } + if (model && (model->rootItem == this)) + child->par = 0; + else + child->par = this; + } + if (!itemsToInsert.isEmpty()) { + while (!stack.isEmpty()) { + QTreeWidgetItem *i = stack.pop(); + i->view = view; + for (int c = 0; c < i->children.count(); ++c) + stack.push(i->children.at(c)); + } + if (model) model->beginInsertItems(this, index, itemsToInsert.count()); + for (int n = 0; n < itemsToInsert.count(); ++n) { + QTreeWidgetItem *child = itemsToInsert.at(n); + this->children.insert(index + n, child); + if (child->par) + d->propagateDisabled(child); + } + if (model) model->endInsertItems(); + } +} + +/*! + \since 4.1 + + Removes the list of children and returns it, otherwise returns an empty list. +*/ +QList<QTreeWidgetItem*> QTreeWidgetItem::takeChildren() +{ + QList<QTreeWidgetItem*> removed; + if (children.count() > 0) { + QTreeModel *model = (view ? qobject_cast<QTreeModel*>(view->model()) : 0); + if (model) { + // This will trigger a layoutChanged signal, thus we might want to optimize + // this function by not emitting the rowsRemoved signal etc to the view. + // On the other hand we also need to make sure that the selectionmodel + // is updated in case we take an item that is selected. + model->executePendingSort(); + } + if (model) model->beginRemoveItems(this, 0, children.count()); + for (int n = 0; n < children.count(); ++n) { + QTreeWidgetItem *item = children.at(n); + item->par = 0; + QStack<QTreeWidgetItem*> stack; + stack.push(item); + while (!stack.isEmpty()) { + QTreeWidgetItem *i = stack.pop(); + i->view = 0; + for (int c = 0; c < i->children.count(); ++c) + stack.push(i->children.at(c)); + } + d->propagateDisabled(item); + } + removed = children; + children.clear(); // detach + if (model) model->endRemoveItems(); + } + return removed; +} + + +void QTreeWidgetItemPrivate::sortChildren(int column, Qt::SortOrder order, bool climb) +{ + QTreeModel *model = (q->view ? qobject_cast<QTreeModel*>(q->view->model()) : 0); + model->sortItems(&q->children, column, order); + if (climb) { + QList<QTreeWidgetItem*>::iterator it = q->children.begin(); + for (; it != q->children.end(); ++it) { + //here we call the private object's method to avoid emitting + //the layoutAboutToBeChanged and layoutChanged signals + (*it)->d->sortChildren(column, order, climb); + } + } +} + +/*! + \internal + + Sorts the children by the value in the given \a column, in the \a order + specified. If \a climb is true, the items below each of the children will + also be sorted. +*/ +void QTreeWidgetItem::sortChildren(int column, Qt::SortOrder order, bool climb) +{ + QTreeModel *model = (view ? qobject_cast<QTreeModel*>(view->model()) : 0); + if (!model) + return; + if (model->isChanging()) + return; + int oldSortColumn = view->d_func()->explicitSortColumn; + view->d_func()->explicitSortColumn = column; + emit model->layoutAboutToBeChanged(); + d->sortChildren(column, order, climb); + emit model->layoutChanged(); + view->d_func()->explicitSortColumn = oldSortColumn; +} + +/*! + \internal + + Calculates the checked state of the item based on the checked state + of its children. E.g. if all children checked => this item is also + checked; if some children checked => this item is partially checked; + if no children checked => this item is unchecked. +*/ +QVariant QTreeWidgetItem::childrenCheckState(int column) const +{ + if (column < 0) + return QVariant(); + bool checkedChildren = false; + bool uncheckedChildren = false; + for (int i = 0; i < children.count(); ++i) { + QVariant value = children.at(i)->data(column, Qt::CheckStateRole); + if (!value.isValid()) + return QVariant(); + + switch (static_cast<Qt::CheckState>(value.toInt())) + { + case Qt::Unchecked: + uncheckedChildren = true; + break; + case Qt::Checked: + checkedChildren = true; + break; + case Qt::PartiallyChecked: + default: + return Qt::PartiallyChecked; + } + } + + if (uncheckedChildren && checkedChildren) + return Qt::PartiallyChecked; + if (uncheckedChildren) + return Qt::Unchecked; + else if (checkedChildren) + return Qt::Checked; + else + return QVariant(); // value was not defined +} + +/*! + \since 4.5 + + Causes the model associated with this item to emit a + \l{QAbstractItemModel::dataChanged()}{dataChanged}() signal for this + item. + + You normally only need to call this function if you have subclassed + QTreeWidgetItem and reimplemented data() and/or setData(). + + \sa setData() +*/ +void QTreeWidgetItem::emitDataChanged() +{ + itemChanged(); +} + +/*! + \internal +*/ +void QTreeWidgetItem::itemChanged() +{ + if (QTreeModel *model = (view ? qobject_cast<QTreeModel*>(view->model()) : 0)) + model->itemChanged(this); +} + +/*! + \internal +*/ +void QTreeWidgetItem::executePendingSort() const +{ + if (QTreeModel *model = (view ? qobject_cast<QTreeModel*>(view->model()) : 0)) + model->executePendingSort(); +} + + +#ifndef QT_NO_DATASTREAM +/*! + \relates QTreeWidgetItem + + Writes the tree widget item \a item to stream \a out. + + This operator uses QTreeWidgetItem::write(). + + \sa {Format of the QDataStream Operators} +*/ +QDataStream &operator<<(QDataStream &out, const QTreeWidgetItem &item) +{ + item.write(out); + return out; +} + +/*! + \relates QTreeWidgetItem + + Reads a tree widget item from stream \a in into \a item. + + This operator uses QTreeWidgetItem::read(). + + \sa {Format of the QDataStream Operators} +*/ +QDataStream &operator>>(QDataStream &in, QTreeWidgetItem &item) +{ + item.read(in); + return in; +} +#endif // QT_NO_DATASTREAM + + +void QTreeWidgetPrivate::_q_emitItemPressed(const QModelIndex &index) +{ + Q_Q(QTreeWidget); + emit q->itemPressed(item(index), index.column()); +} + +void QTreeWidgetPrivate::_q_emitItemClicked(const QModelIndex &index) +{ + Q_Q(QTreeWidget); + emit q->itemClicked(item(index), index.column()); +} + +void QTreeWidgetPrivate::_q_emitItemDoubleClicked(const QModelIndex &index) +{ + Q_Q(QTreeWidget); + emit q->itemDoubleClicked(item(index), index.column()); +} + +void QTreeWidgetPrivate::_q_emitItemActivated(const QModelIndex &index) +{ + Q_Q(QTreeWidget); + emit q->itemActivated(item(index), index.column()); +} + +void QTreeWidgetPrivate::_q_emitItemEntered(const QModelIndex &index) +{ + Q_Q(QTreeWidget); + emit q->itemEntered(item(index), index.column()); +} + +void QTreeWidgetPrivate::_q_emitItemChanged(const QModelIndex &index) +{ + Q_Q(QTreeWidget); + QTreeWidgetItem *indexItem = item(index); + if (indexItem) + emit q->itemChanged(indexItem, index.column()); +} + +void QTreeWidgetPrivate::_q_emitItemExpanded(const QModelIndex &index) +{ + Q_Q(QTreeWidget); + emit q->itemExpanded(item(index)); +} + +void QTreeWidgetPrivate::_q_emitItemCollapsed(const QModelIndex &index) +{ + Q_Q(QTreeWidget); + emit q->itemCollapsed(item(index)); +} + +void QTreeWidgetPrivate::_q_emitCurrentItemChanged(const QModelIndex ¤t, + const QModelIndex &previous) +{ + Q_Q(QTreeWidget); + QTreeWidgetItem *currentItem = item(current); + QTreeWidgetItem *previousItem = item(previous); + emit q->currentItemChanged(currentItem, previousItem); +} + +void QTreeWidgetPrivate::_q_sort() +{ + Q_Q(QTreeWidget); + if (sortingEnabled) { + int column = q->header()->sortIndicatorSection(); + Qt::SortOrder order = q->header()->sortIndicatorOrder(); + model()->sort(column, order); + } +} + +void QTreeWidgetPrivate::_q_selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) +{ + Q_Q(QTreeWidget); + QModelIndexList indices = selected.indexes(); + int i; + QTreeModel *m = model(); + for (i = 0; i < indices.count(); ++i) { + QTreeWidgetItem *item = m->item(indices.at(i)); + item->d->selected = true; + } + + indices = deselected.indexes(); + for (i = 0; i < indices.count(); ++i) { + QTreeWidgetItem *item = m->item(indices.at(i)); + item->d->selected = false; + } + + emit q->itemSelectionChanged(); +} + +void QTreeWidgetPrivate::_q_dataChanged(const QModelIndex &topLeft, + const QModelIndex &bottomRight) +{ + Q_Q(QTreeWidget); + if (sortingEnabled && topLeft.isValid() && bottomRight.isValid() + && !model()->sortPendingTimer.isActive()) { + int column = q->header()->sortIndicatorSection(); + if (column >= topLeft.column() && column <= bottomRight.column()) { + Qt::SortOrder order = q->header()->sortIndicatorOrder(); + model()->ensureSorted(column, order, topLeft.row(), + bottomRight.row(), topLeft.parent()); + } + } +} + +/*! + \class QTreeWidget + + \brief The QTreeWidget class provides a tree view that uses a predefined + tree model. + + \ingroup model-view + \mainclass + + The QTreeWidget class is a convenience class that provides a standard + tree widget with a classic item-based interface similar to that used by + the QListView class in Qt 3. + This class is based on Qt's Model/View architecture and uses a default + model to hold items, each of which is a QTreeWidgetItem. + + Developers who do not need the flexibility of the Model/View framework + can use this class to create simple hierarchical lists very easily. A more + flexible approach involves combining a QTreeView with a standard item model. + This allows the storage of data to be separated from its representation. + + In its simplest form, a tree widget can be constructed in the following way: + + \snippet doc/src/snippets/code/src_gui_itemviews_qtreewidget.cpp 0 + + Before items can be added to the tree widget, the number of columns must + be set with setColumnCount(). This allows each item to have one or more + labels or other decorations. The number of columns in use can be found + with the columnCount() function. + + The tree can have a header that contains a section for each column in + the widget. It is easiest to set up the labels for each section by + supplying a list of strings with setHeaderLabels(), but a custom header + can be constructed with a QTreeWidgetItem and inserted into the tree + with the setHeaderItem() function. + + The items in the tree can be sorted by column according to a predefined + sort order. If sorting is enabled, the user can sort the items by clicking + on a column header. Sorting can be enabled or disabled by calling + \l{QTreeView::setSortingEnabled()}{setSortingEnabled()}. The + \l{QTreeView::isSortingEnabled()}{isSortingEnabled()} function indicates + whether sorting is enabled. + + \table 100% + \row \o \inlineimage windowsxp-treeview.png Screenshot of a Windows XP style tree widget + \o \inlineimage macintosh-treeview.png Screenshot of a Macintosh style tree widget + \o \inlineimage plastique-treeview.png Screenshot of a Plastique style tree widget + \row \o A \l{Windows XP Style Widget Gallery}{Windows XP style} tree widget. + \o A \l{Macintosh Style Widget Gallery}{Macintosh style} tree widget. + \o A \l{Plastique Style Widget Gallery}{Plastique style} tree widget. + \endtable + + \sa QTreeWidgetItem, QTreeWidgetItemIterator, QTreeView, + {Model/View Programming}, {Settings Editor Example} +*/ + +/*! + \property QTreeWidget::columnCount + \brief the number of columns displayed in the tree widget + + By default, this property has a value of 1. +*/ + +/*! + \fn void QTreeWidget::itemActivated(QTreeWidgetItem *item, int column) + + This signal is emitted when the user activates an item by single- + or double-clicking (depending on the platform, i.e. on the + QStyle::SH_ItemView_ActivateItemOnSingleClick style hint) or + pressing a special key (e.g., \key Enter). + + The specified \a item is the item that was clicked, or 0 if no + item was clicked. The \a column is the item's column that was + clicked, or -1 if no item was clicked. +*/ + +/*! + \fn void QTreeWidget::itemPressed(QTreeWidgetItem *item, int column) + + This signal is emitted when the user presses a mouse button inside + the widget. + + The specified \a item is the item that was clicked, or 0 if no + item was clicked. The \a column is the item's column that was + clicked, or -1 if no item was clicked. +*/ + +/*! + \fn void QTreeWidget::itemClicked(QTreeWidgetItem *item, int column) + + This signal is emitted when the user clicks inside the widget. + + The specified \a item is the item that was clicked. The \a column is the + item's column that was clicked. If no item was clicked, no signal will be + emitted. +*/ + +/*! + \fn void QTreeWidget::itemDoubleClicked(QTreeWidgetItem *item, int column) + + This signal is emitted when the user double clicks inside the + widget. + + The specified \a item is the item that was clicked, or 0 if no + item was clicked. The \a column is the item's column that was + clicked. If no item was double clicked, no signal will be emitted. +*/ + +/*! + \fn void QTreeWidget::itemExpanded(QTreeWidgetItem *item) + + This signal is emitted when the specified \a item is expanded so that + all of its children are displayed. + + \note This signal will not be emitted if an item changes its state when + expandAll() is invoked. + + \sa isItemExpanded(), itemCollapsed(), expandItem() +*/ + +/*! + \fn void QTreeWidget::itemCollapsed(QTreeWidgetItem *item) + + This signal is emitted when the specified \a item is collapsed so that + none of its children are displayed. + + \note This signal will not be emitted if an item changes its state when + collapseAll() is invoked. + + \sa isItemExpanded(), itemExpanded(), collapseItem() +*/ + +/*! + \fn void QTreeWidget::currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous) + + This signal is emitted when the current item changes. The current + item is specified by \a current, and this replaces the \a previous + current item. + + \sa setCurrentItem() +*/ + +/*! + \fn void QTreeWidget::itemSelectionChanged() + + This signal is emitted when the selection changes in the tree widget. + The current selection can be found with selectedItems(). +*/ + +/*! + \fn void QTreeWidget::itemEntered(QTreeWidgetItem *item, int column) + + This signal is emitted when the mouse cursor enters an \a item over the + specified \a column. + QTreeWidget mouse tracking needs to be enabled for this feature to work. +*/ + +/*! + \fn void QTreeWidget::itemChanged(QTreeWidgetItem *item, int column) + + This signal is emitted when the contents of the \a column in the specified + \a item changes. +*/ + +/*! + \since 4.3 + + \fn void QTreeWidget::removeItemWidget(QTreeWidgetItem *item, int column) + + Removes the widget set in the given \a item in the given \a column. +*/ + +/*! + Constructs a tree widget with the given \a parent. +*/ +QTreeWidget::QTreeWidget(QWidget *parent) + : QTreeView(*new QTreeWidgetPrivate(), parent) +{ + QTreeView::setModel(new QTreeModel(1, this)); + connect(this, SIGNAL(pressed(QModelIndex)), + SLOT(_q_emitItemPressed(QModelIndex))); + connect(this, SIGNAL(clicked(QModelIndex)), + SLOT(_q_emitItemClicked(QModelIndex))); + connect(this, SIGNAL(doubleClicked(QModelIndex)), + SLOT(_q_emitItemDoubleClicked(QModelIndex))); + connect(this, SIGNAL(activated(QModelIndex)), + SLOT(_q_emitItemActivated(QModelIndex))); + connect(this, SIGNAL(entered(QModelIndex)), + SLOT(_q_emitItemEntered(QModelIndex))); + connect(this, SIGNAL(expanded(QModelIndex)), + SLOT(_q_emitItemExpanded(QModelIndex))); + connect(this, SIGNAL(collapsed(QModelIndex)), + SLOT(_q_emitItemCollapsed(QModelIndex))); + connect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_emitCurrentItemChanged(QModelIndex,QModelIndex))); + connect(model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_emitItemChanged(QModelIndex))); + connect(model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_dataChanged(QModelIndex,QModelIndex))); + connect(model(), SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sort())); + connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + this, SLOT(_q_selectionChanged(QItemSelection,QItemSelection))); + header()->setClickable(false); +} + +/*! + Destroys the tree widget and all its items. +*/ + +QTreeWidget::~QTreeWidget() +{ +} + +/* + Retuns the number of header columns in the view. + + \sa sortColumn(), currentColumn(), topLevelItemCount() +*/ + +int QTreeWidget::columnCount() const +{ + Q_D(const QTreeWidget); + return d->model()->columnCount(); +} + +/* + Sets the number of header \a columns in the tree widget. +*/ + +void QTreeWidget::setColumnCount(int columns) +{ + Q_D(QTreeWidget); + if (columns < 0) + return; + d->model()->setColumnCount(columns); +} + +/*! + \since 4.2 + + Returns the tree widget's invisible root item. + + The invisible root item provides access to the tree widget's top-level items + through the QTreeWidgetItem API, making it possible to write functions that + can treat top-level items and their children in a uniform way; for example, + recursive functions. +*/ + +QTreeWidgetItem *QTreeWidget::invisibleRootItem() const +{ + Q_D(const QTreeWidget); + return d->model()->rootItem; +} + +/*! + Returns the top level item at the given \a index, or 0 if the item does + not exist. + + \sa topLevelItemCount(), insertTopLevelItem() +*/ + +QTreeWidgetItem *QTreeWidget::topLevelItem(int index) const +{ + Q_D(const QTreeWidget); + return d->model()->rootItem->child(index); +} + +/*! + \property QTreeWidget::topLevelItemCount + \brief the number of top-level items + + By default, this property has a value of 0. + + \sa columnCount(), currentItem() +*/ + +int QTreeWidget::topLevelItemCount() const +{ + Q_D(const QTreeWidget); + return d->model()->rootItem->childCount(); +} + +/*! + Inserts the \a item at \a index in the top level in the view. + + If the item has already been inserted somewhere else it wont be inserted. + + \sa addTopLevelItem(), columnCount() +*/ + +void QTreeWidget::insertTopLevelItem(int index, QTreeWidgetItem *item) +{ + Q_D(QTreeWidget); + d->model()->rootItem->insertChild(index, item); +} + +/*! + \since 4.1 + + Appends the \a item as a top-level item in the widget. + + \sa insertTopLevelItem() +*/ +void QTreeWidget::addTopLevelItem(QTreeWidgetItem *item) +{ + insertTopLevelItem(topLevelItemCount(), item); +} + +/*! + Removes the top-level item at the given \a index in the tree and + returns it, otherwise returns 0; + + \sa insertTopLevelItem(), topLevelItem(), topLevelItemCount() +*/ + +QTreeWidgetItem *QTreeWidget::takeTopLevelItem(int index) +{ + Q_D(QTreeWidget); + return d->model()->rootItem->takeChild(index); +} + +/*! + \internal +*/ +int QTreeWidget::indexOfTopLevelItem(QTreeWidgetItem *item) +{ + Q_D(QTreeWidget); + d->model()->executePendingSort(); + return d->model()->rootItem->children.indexOf(item); +} + +/*! + Returns the index of the given top-level \a item, or -1 if the item + cannot be found. + + \sa sortItems(), topLevelItemCount() + */ +int QTreeWidget::indexOfTopLevelItem(QTreeWidgetItem *item) const +{ + Q_D(const QTreeWidget); + d->model()->executePendingSort(); + return d->model()->rootItem->children.indexOf(item); +} + +/*! + \since 4.1 + + Inserts the list of \a items at \a index in the top level in the view. + + Items that have already been inserted somewhere else wont be inserted. + + \sa addTopLevelItems() +*/ +void QTreeWidget::insertTopLevelItems(int index, const QList<QTreeWidgetItem*> &items) +{ + Q_D(QTreeWidget); + d->model()->rootItem->insertChildren(index, items); +} + +/*! + Appends the list of \a items as a top-level items in the widget. + + \sa insertTopLevelItems() +*/ +void QTreeWidget::addTopLevelItems(const QList<QTreeWidgetItem*> &items) +{ + insertTopLevelItems(topLevelItemCount(), items); +} + +/*! + Returns the item used for the tree widget's header. + + \sa setHeaderItem() +*/ + +QTreeWidgetItem *QTreeWidget::headerItem() const +{ + Q_D(const QTreeWidget); + return d->model()->headerItem; +} + +/*! + Sets the header \a item for the tree widget. The label for each column in + the header is supplied by the corresponding label in the item. + + The tree widget takes ownership of the item. + + \sa headerItem(), setHeaderLabels() +*/ + +void QTreeWidget::setHeaderItem(QTreeWidgetItem *item) +{ + Q_D(QTreeWidget); + if (!item) + return; + item->view = this; + + int oldCount = columnCount(); + if (oldCount < item->columnCount()) + d->model()->beginInsertColumns(QModelIndex(), oldCount, item->columnCount()); + else + d->model()->beginRemoveColumns(QModelIndex(), item->columnCount(), oldCount); + delete d->model()->headerItem; + d->model()->headerItem = item; + if (oldCount < item->columnCount()) + d->model()->endInsertColumns(); + else + d->model()->endRemoveColumns(); + d->model()->headerDataChanged(Qt::Horizontal, 0, oldCount); +} + + +/*! + Adds a column in the header for each item in the \a labels list, and sets + the label for each column. + + Note that setHeaderLabels() won't remove existing columns. + + \sa setHeaderItem(), setHeaderLabel() +*/ +void QTreeWidget::setHeaderLabels(const QStringList &labels) +{ + Q_D(QTreeWidget); + if (columnCount() < labels.count()) + setColumnCount(labels.count()); + QTreeModel *model = d->model(); + QTreeWidgetItem *item = model->headerItem; + for (int i = 0; i < labels.count(); ++i) + item->setText(i, labels.at(i)); +} + +/*! + \fn void QTreeWidget::setHeaderLabel(const QString &label) + \since 4.2 + + Same as setHeaderLabels(QStringList(\a label)). +*/ + +/*! + Returns the current item in the tree widget. + + \sa setCurrentItem(), currentItemChanged() +*/ +QTreeWidgetItem *QTreeWidget::currentItem() const +{ + Q_D(const QTreeWidget); + return d->item(currentIndex()); +} + +/*! + \since 4.1 + Returns the current column in the tree widget. + + \sa setCurrentItem(), columnCount() +*/ +int QTreeWidget::currentColumn() const +{ + return currentIndex().column(); +} + +/*! + Sets the current \a item in the tree widget. + + Depending on the current selection mode, the item may also be selected. + + \sa currentItem(), currentItemChanged() +*/ +void QTreeWidget::setCurrentItem(QTreeWidgetItem *item) +{ + setCurrentItem(item, 0); +} + +/*! + \since 4.1 + Sets the current \a item in the tree widget and the current column to \a column. + + \sa currentItem() +*/ +void QTreeWidget::setCurrentItem(QTreeWidgetItem *item, int column) +{ + Q_D(QTreeWidget); + setCurrentIndex(d->index(item, column)); +} + +/*! + \since 4.4 + Sets the current \a item in the tree widget and the current column to \a column, + using the given \a command. + + \sa currentItem() +*/ +void QTreeWidget::setCurrentItem(QTreeWidgetItem *item, int column, + QItemSelectionModel::SelectionFlags command) +{ + Q_D(QTreeWidget); + d->selectionModel->setCurrentIndex(d->index(item, column), command); +} + + +/*! + Returns a pointer to the item at the coordinates \a p. + + \sa visualItemRect() +*/ +QTreeWidgetItem *QTreeWidget::itemAt(const QPoint &p) const +{ + Q_D(const QTreeWidget); + return d->item(indexAt(p)); +} + +/*! + \fn QTreeWidgetItem *QTreeWidget::itemAt(int x, int y) const + \overload + + Returns a pointer to the item at the coordinates (\a x, \a y). +*/ + +/*! + Returns the rectangle on the viewport occupied by the item at \a item. + + \sa itemAt() +*/ +QRect QTreeWidget::visualItemRect(const QTreeWidgetItem *item) const +{ + Q_D(const QTreeWidget); + return visualRect(d->index(item)); +} + +/*! + \since 4.1 + + Returns the column used to sort the contents of the widget. + + \sa sortItems() +*/ +int QTreeWidget::sortColumn() const +{ + Q_D(const QTreeWidget); + return (d->explicitSortColumn != -1 + ? d->explicitSortColumn + : header()->sortIndicatorSection()); +} + +/*! + Sorts the items in the widget in the specified \a order by the values in + the given \a column. + + \sa sortColumn() +*/ + +void QTreeWidget::sortItems(int column, Qt::SortOrder order) +{ + Q_D(QTreeWidget); + header()->setSortIndicator(column, order); + d->model()->sort(column, order); +} + +/*! + \internal + + ### Qt 5: remove +*/ +void QTreeWidget::setSortingEnabled(bool enable) +{ + QTreeView::setSortingEnabled(enable); +} + +/*! + \internal + + ### Qt 5: remove +*/ +bool QTreeWidget::isSortingEnabled() const +{ + return QTreeView::isSortingEnabled(); +} + +/*! + Starts editing the \a item in the given \a column if it is editable. +*/ + +void QTreeWidget::editItem(QTreeWidgetItem *item, int column) +{ + Q_D(QTreeWidget); + edit(d->index(item, column)); +} + +/*! + Opens a persistent editor for the \a item in the given \a column. + + \sa closePersistentEditor() +*/ + +void QTreeWidget::openPersistentEditor(QTreeWidgetItem *item, int column) +{ + Q_D(QTreeWidget); + QAbstractItemView::openPersistentEditor(d->index(item, column)); +} + +/*! + Closes the persistent editor for the \a item in the given \a column. + + This function has no effect if no persistent editor is open for this + combination of item and column. + + \sa openPersistentEditor() +*/ + +void QTreeWidget::closePersistentEditor(QTreeWidgetItem *item, int column) +{ + Q_D(QTreeWidget); + QAbstractItemView::closePersistentEditor(d->index(item, column)); +} + +/*! + \since 4.1 + + Returns the widget displayed in the cell specified by \a item and the given \a column. + + \note The tree takes ownership of the widget. + +*/ +QWidget *QTreeWidget::itemWidget(QTreeWidgetItem *item, int column) const +{ + Q_D(const QTreeWidget); + return QAbstractItemView::indexWidget(d->index(item, column)); +} + +/*! + \since 4.1 + + Sets the given \a widget to be displayed in the cell specified by + the given \a item and \a column. + + Note that the given \a widget's \l {QWidget}{autoFillBackground} + property must be set to true, otherwise the widget's background will + be transparent, showing both the model data and the tree widget + item. + + This function should only be used to display static content in the + place of a tree widget item. If you want to display custom dynamic + content or implement a custom editor widget, use QTreeView and + subclass QItemDelegate instead. + + \note The tree takes ownership of the widget. + + \sa {Delegate Classes} +*/ +void QTreeWidget::setItemWidget(QTreeWidgetItem *item, int column, QWidget *widget) +{ + Q_D(QTreeWidget); + QAbstractItemView::setIndexWidget(d->index(item, column), widget); +} + +/*! + Returns true if the \a item is selected; otherwise returns false. + + \sa itemSelectionChanged() + + \obsolete + + This function is deprecated. Use \l{QTreeWidgetItem::isSelected()} instead. +*/ +bool QTreeWidget::isItemSelected(const QTreeWidgetItem *item) const +{ + if (!item) + return false; + return item->d->selected; +} + +/*! + If \a select is true, the given \a item is selected; otherwise it is + deselected. + + \sa itemSelectionChanged() + + \obsolete + + This function is deprecated. Use \l{QTreeWidgetItem::setSelected()} instead. +*/ +void QTreeWidget::setItemSelected(const QTreeWidgetItem *item, bool select) +{ + Q_D(QTreeWidget); + + if (!item) + return; + + selectionModel()->select(d->index(item), (select ? QItemSelectionModel::Select + : QItemSelectionModel::Deselect) + |QItemSelectionModel::Rows); + item->d->selected = select; +} + +/*! + Returns a list of all selected non-hidden items. + + \sa itemSelectionChanged() +*/ +QList<QTreeWidgetItem*> QTreeWidget::selectedItems() const +{ + Q_D(const QTreeWidget); + QModelIndexList indexes = selectionModel()->selectedIndexes(); + QList<QTreeWidgetItem*> items; + for (int i = 0; i < indexes.count(); ++i) { + QTreeWidgetItem *item = d->item(indexes.at(i)); + if (isItemHidden(item) || items.contains(item)) // ### slow, optimize later + continue; + items.append(item); + } + return items; +} + +/*! + Returns a list of items that match the given \a text, using the given \a flags, in the given \a column. +*/ +QList<QTreeWidgetItem*> QTreeWidget::findItems(const QString &text, Qt::MatchFlags flags, int column) const +{ + Q_D(const QTreeWidget); + QModelIndexList indexes = d->model()->match(model()->index(0, column, QModelIndex()), + Qt::DisplayRole, text, -1, flags); + QList<QTreeWidgetItem*> items; + for (int i = 0; i < indexes.size(); ++i) + items.append(d->item(indexes.at(i))); + return items; +} + +/*! + Returns true if the \a item is explicitly hidden, otherwise returns false. + + \obsolete + + This function is deprecated. Use \l{QTreeWidgetItem::isHidden()} instead. +*/ +bool QTreeWidget::isItemHidden(const QTreeWidgetItem *item) const +{ + Q_D(const QTreeWidget); + if (item == d->model()->headerItem) + return header()->isHidden(); + if (d->hiddenIndexes.isEmpty()) + return false; + QTreeModel::SkipSorting skipSorting(d->model()); + return d->isRowHidden(d->index(item)); +} + +/*! + Hides the given \a item if \a hide is true; otherwise shows the item. + + \sa itemChanged() + + \obsolete + + This function is deprecated. Use \l{QTreeWidgetItem::setHidden()} instead. +*/ +void QTreeWidget::setItemHidden(const QTreeWidgetItem *item, bool hide) +{ + Q_D(QTreeWidget); + if (item == d->model()->headerItem) { + header()->setHidden(hide); + } else { + const QModelIndex index = d->index(item); + setRowHidden(index.row(), index.parent(), hide); + } +} + +/*! + Returns true if the given \a item is open; otherwise returns false. + + \sa itemExpanded() + + \obsolete + + This function is deprecated. Use \l{QTreeWidgetItem::isExpanded()} instead. +*/ +bool QTreeWidget::isItemExpanded(const QTreeWidgetItem *item) const +{ + Q_D(const QTreeWidget); + QTreeModel::SkipSorting skipSorting(d->model()); + return isExpanded(d->index(item)); +} + +/*! + Sets the item referred to by \a item to either closed or opened, + depending on the value of \a expand. + + \sa expandItem(), collapseItem(), itemExpanded() + + \obsolete + + This function is deprecated. Use \l{QTreeWidgetItem::setExpanded()} instead. +*/ +void QTreeWidget::setItemExpanded(const QTreeWidgetItem *item, bool expand) +{ + Q_D(QTreeWidget); + QTreeModel::SkipSorting skipSorting(d->model()); + setExpanded(d->index(item), expand); +} + +/*! + \since 4.3 + + Returns true if the given \a item is set to show only one section over all columns; + otherwise returns false. + + \sa setFirstItemColumnSpanned() +*/ +bool QTreeWidget::isFirstItemColumnSpanned(const QTreeWidgetItem *item) const +{ + Q_D(const QTreeWidget); + if (item == d->model()->headerItem) + return false; // We can't set the header items to spanning + const QModelIndex index = d->index(item); + return isFirstColumnSpanned(index.row(), index.parent()); +} + +/*! + \since 4.3 + + Sets the given \a item to only show one section for all columns if \a span is true; + otherwise the item will show one section per column. + + \sa isFirstItemColumnSpanned() +*/ +void QTreeWidget::setFirstItemColumnSpanned(const QTreeWidgetItem *item, bool span) +{ + Q_D(QTreeWidget); + if (item == d->model()->headerItem) + return; // We can't set header items to spanning + const QModelIndex index = d->index(item); + setFirstColumnSpanned(index.row(), index.parent(), span); +} + +/*! + \since 4.3 + + Returns the item above the given \a item. +*/ +QTreeWidgetItem *QTreeWidget::itemAbove(const QTreeWidgetItem *item) const +{ + Q_D(const QTreeWidget); + if (item == d->model()->headerItem) + return 0; + const QModelIndex index = d->index(item); + const QModelIndex above = indexAbove(index); + return d->item(above); +} + +/*! + \since 4.3 + + Returns the item visually below the given \a item. +*/ +QTreeWidgetItem *QTreeWidget::itemBelow(const QTreeWidgetItem *item) const +{ + Q_D(const QTreeWidget); + if (item == d->model()->headerItem) + return 0; + const QModelIndex index = d->index(item); + const QModelIndex below = indexBelow(index); + return d->item(below); +} + +/*! + \reimp + */ +void QTreeWidget::setSelectionModel(QItemSelectionModel *selectionModel) +{ + Q_D(QTreeWidget); + QTreeView::setSelectionModel(selectionModel); + QItemSelection newSelection = selectionModel->selection(); + if (!newSelection.isEmpty()) + d->_q_selectionChanged(newSelection, QItemSelection()); +} + +/*! + Ensures that the \a item is visible, scrolling the view if necessary using + the specified \a hint. + + \sa currentItem(), itemAt(), topLevelItem() +*/ +void QTreeWidget::scrollToItem(const QTreeWidgetItem *item, QAbstractItemView::ScrollHint hint) +{ + Q_D(QTreeWidget); + QTreeView::scrollTo(d->index(item), hint); +} + +/*! + Expands the \a item. This causes the tree containing the item's children + to be expanded. + + \sa collapseItem(), currentItem(), itemAt(), topLevelItem(), itemExpanded() +*/ +void QTreeWidget::expandItem(const QTreeWidgetItem *item) +{ + Q_D(QTreeWidget); + QTreeModel::SkipSorting skipSorting(d->model()); + expand(d->index(item)); +} + +/*! + Closes the \a item. This causes the tree containing the item's children + to be collapsed. + + \sa expandItem(), currentItem(), itemAt(), topLevelItem() +*/ +void QTreeWidget::collapseItem(const QTreeWidgetItem *item) +{ + Q_D(QTreeWidget); + QTreeModel::SkipSorting skipSorting(d->model()); + collapse(d->index(item)); +} + +/*! + Clears the tree widget by removing all of its items and selections. + + \bold{Note:} Since each item is removed from the tree widget before being + deleted, the return value of QTreeWidgetItem::treeWidget() will be invalid + when called from an item's destructor. + + \sa takeTopLevelItem(), topLevelItemCount(), columnCount() +*/ +void QTreeWidget::clear() +{ + Q_D(QTreeWidget); + selectionModel()->clear(); + d->model()->clear(); +} + +/*! + Returns a list of MIME types that can be used to describe a list of + treewidget items. + + \sa mimeData() +*/ +QStringList QTreeWidget::mimeTypes() const +{ + return model()->QAbstractItemModel::mimeTypes(); +} + +/*! + Returns an object that contains a serialized description of the specified + \a items. The format used to describe the items is obtained from the + mimeTypes() function. + + If the list of items is empty, 0 is returned rather than a serialized + empty list. +*/ +QMimeData *QTreeWidget::mimeData(const QList<QTreeWidgetItem*> items) const +{ + Q_D(const QTreeWidget); + if (d->model()->cachedIndexes.isEmpty()) { + QList<QModelIndex> indexes; + for (int i = 0; i < items.count(); ++i) { + QTreeWidgetItem *item = items.at(i); + for (int c = 0; c < item->values.count(); ++c) { + indexes << indexFromItem(item, c); + } + } + return model()->QAbstractItemModel::mimeData(indexes); + } + return d->model()->internalMimeData(); +} + +/*! + Handles the \a data supplied by a drag and drop operation that ended with + the given \a action in the \a index in the given \a parent item. + + The default implementation returns true if the drop was + successfully handled by decoding the mime data and inserting it + into the model; otherwise it returns false. + + \sa supportedDropActions() +*/ +bool QTreeWidget::dropMimeData(QTreeWidgetItem *parent, int index, + const QMimeData *data, Qt::DropAction action) +{ + QModelIndex idx; + if (parent) idx = indexFromItem(parent); + return model()->QAbstractItemModel::dropMimeData(data, action , index, 0, idx); +} + +/*! + Returns the drop actions supported by this view. + + \sa Qt::DropActions +*/ +Qt::DropActions QTreeWidget::supportedDropActions() const +{ + return model()->QAbstractItemModel::supportedDropActions() | Qt::MoveAction; +} + +/*! + \obsolete + Returns an empty list + + \sa mimeData() +*/ +QList<QTreeWidgetItem*> QTreeWidget::items(const QMimeData *data) const +{ + Q_UNUSED(data); + return QList<QTreeWidgetItem*>(); +} + +/*! + Returns the QModelIndex assocated with the given \a item in the given \a column. + + \sa itemFromIndex(), topLevelItem() +*/ +QModelIndex QTreeWidget::indexFromItem(QTreeWidgetItem *item, int column) const +{ + Q_D(const QTreeWidget); + return d->index(item, column); +} + +/*! + Returns a pointer to the QTreeWidgetItem assocated with the given \a index. + + \sa indexFromItem() +*/ +QTreeWidgetItem *QTreeWidget::itemFromIndex(const QModelIndex &index) const +{ + Q_D(const QTreeWidget); + return d->item(index); +} + +#ifndef QT_NO_DRAGANDDROP +/*! \reimp */ +void QTreeWidget::dropEvent(QDropEvent *event) { + Q_D(QTreeWidget); + if (event->source() == this && (event->dropAction() == Qt::MoveAction || + dragDropMode() == QAbstractItemView::InternalMove)) { + QModelIndex topIndex; + int col = -1; + int row = -1; + if (d->dropOn(event, &row, &col, &topIndex)) { + QList<QModelIndex> idxs = selectedIndexes(); + QList<QPersistentModelIndex> indexes; + for (int i = 0; i < idxs.count(); i++) + indexes.append(idxs.at(i)); + + if (indexes.contains(topIndex)) + return; + + // When removing items the drop location could shift + QPersistentModelIndex dropRow = model()->index(row, col, topIndex); + + // Remove the items + QList<QTreeWidgetItem *> taken; + for (int i = indexes.count() - 1; i >= 0; --i) { + QTreeWidgetItem *parent = itemFromIndex(indexes.at(i)); + if (!parent || !parent->parent()) { + taken.append(takeTopLevelItem(indexes.at(i).row())); + } else { + taken.append(parent->parent()->takeChild(indexes.at(i).row())); + } + } + + // insert them back in at their new positions + for (int i = 0; i < indexes.count(); ++i) { + // Either at a specific point or appended + if (row == -1) { + if (topIndex.isValid()) { + QTreeWidgetItem *parent = itemFromIndex(topIndex); + parent->insertChild(parent->childCount(), taken.takeFirst()); + } else { + insertTopLevelItem(topLevelItemCount(), taken.takeFirst()); + } + } else { + int r = dropRow.row() >= 0 ? dropRow.row() : row; + if (topIndex.isValid()) { + QTreeWidgetItem *parent = itemFromIndex(topIndex); + parent->insertChild(qMin(r, parent->childCount()), taken.takeFirst()); + } else { + insertTopLevelItem(qMin(r, topLevelItemCount()), taken.takeFirst()); + } + } + } + + event->accept(); + // Don't want QAbstractItemView to delete it because it was "moved" we already did it + event->setDropAction(Qt::CopyAction); + } + } + + QTreeView::dropEvent(event); +} +#endif + +/*! + \reimp +*/ + +void QTreeWidget::setModel(QAbstractItemModel * /*model*/) +{ + Q_ASSERT(!"QTreeWidget::setModel() - Changing the model of the QTreeWidget is not allowed."); +} + +/*! + \reimp +*/ +bool QTreeWidget::event(QEvent *e) +{ + Q_D(QTreeWidget); + if (e->type() == QEvent::Polish) + d->model()->executePendingSort(); + return QTreeView::event(e); +} + +QT_END_NAMESPACE + +#include "moc_qtreewidget.cpp" + +#endif // QT_NO_TREEWIDGET diff --git a/src/gui/itemviews/qtreewidget.h b/src/gui/itemviews/qtreewidget.h new file mode 100644 index 0000000..4bacce7 --- /dev/null +++ b/src/gui/itemviews/qtreewidget.h @@ -0,0 +1,432 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTREEWIDGET_H +#define QTREEWIDGET_H + +#include <QtGui/qtreeview.h> +#include <QtGui/qtreewidgetitemiterator.h> +#include <QtCore/qvariant.h> +#include <QtCore/qvector.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_TREEWIDGET + +class QTreeWidget; +class QTreeModel; +class QWidgetItemData; +class QTreeWidgetItemPrivate; + +class Q_GUI_EXPORT QTreeWidgetItem +{ + friend class QTreeModel; + friend class QTreeWidget; + friend class QTreeWidgetPrivate; + friend class QTreeWidgetItemIterator; + friend class QTreeWidgetItemPrivate; +public: + enum ItemType { Type = 0, UserType = 1000 }; + explicit QTreeWidgetItem(int type = Type); + QTreeWidgetItem(const QStringList &strings, int type = Type); + explicit QTreeWidgetItem(QTreeWidget *view, int type = Type); + QTreeWidgetItem(QTreeWidget *view, const QStringList &strings, int type = Type); + QTreeWidgetItem(QTreeWidget *view, QTreeWidgetItem *after, int type = Type); + explicit QTreeWidgetItem(QTreeWidgetItem *parent, int type = Type); + QTreeWidgetItem(QTreeWidgetItem *parent, const QStringList &strings, int type = Type); + QTreeWidgetItem(QTreeWidgetItem *parent, QTreeWidgetItem *after, int type = Type); + QTreeWidgetItem(const QTreeWidgetItem &other); + virtual ~QTreeWidgetItem(); + + virtual QTreeWidgetItem *clone() const; + + inline QTreeWidget *treeWidget() const { return view; } + + inline void setSelected(bool select); + inline bool isSelected() const; + + inline void setHidden(bool hide); + inline bool isHidden() const; + + inline void setExpanded(bool expand); + inline bool isExpanded() const; + + inline void setFirstColumnSpanned(bool span); + inline bool isFirstColumnSpanned() const; + + inline void setDisabled(bool disabled); + inline bool isDisabled() const; + + enum ChildIndicatorPolicy { ShowIndicator, DontShowIndicator, DontShowIndicatorWhenChildless }; + void setChildIndicatorPolicy(QTreeWidgetItem::ChildIndicatorPolicy policy); + QTreeWidgetItem::ChildIndicatorPolicy childIndicatorPolicy() const; + + Qt::ItemFlags flags() const; + void setFlags(Qt::ItemFlags flags); + + inline QString text(int column) const + { return data(column, Qt::DisplayRole).toString(); } + inline void setText(int column, const QString &text); + + inline QIcon icon(int column) const + { return qvariant_cast<QIcon>(data(column, Qt::DecorationRole)); } + inline void setIcon(int column, const QIcon &icon); + + inline QString statusTip(int column) const + { return data(column, Qt::StatusTipRole).toString(); } + inline void setStatusTip(int column, const QString &statusTip); + +#ifndef QT_NO_TOOLTIP + inline QString toolTip(int column) const + { return data(column, Qt::ToolTipRole).toString(); } + inline void setToolTip(int column, const QString &toolTip); +#endif + +#ifndef QT_NO_WHATSTHIS + inline QString whatsThis(int column) const + { return data(column, Qt::WhatsThisRole).toString(); } + inline void setWhatsThis(int column, const QString &whatsThis); +#endif + + inline QFont font(int column) const + { return qvariant_cast<QFont>(data(column, Qt::FontRole)); } + inline void setFont(int column, const QFont &font); + + inline int textAlignment(int column) const + { return data(column, Qt::TextAlignmentRole).toInt(); } + inline void setTextAlignment(int column, int alignment) + { setData(column, Qt::TextAlignmentRole, alignment); } + + inline QColor backgroundColor(int column) const + { return qvariant_cast<QColor>(data(column, Qt::BackgroundColorRole)); } + inline void setBackgroundColor(int column, const QColor &color) + { setData(column, Qt::BackgroundColorRole, color); } + + inline QBrush background(int column) const + { return qvariant_cast<QBrush>(data(column, Qt::BackgroundRole)); } + inline void setBackground(int column, const QBrush &brush) + { setData(column, Qt::BackgroundRole, brush); } + + inline QColor textColor(int column) const + { return qvariant_cast<QColor>(data(column, Qt::TextColorRole)); } + inline void setTextColor(int column, const QColor &color) + { setData(column, Qt::TextColorRole, color); } + + inline QBrush foreground(int column) const + { return qvariant_cast<QBrush>(data(column, Qt::ForegroundRole)); } + inline void setForeground(int column, const QBrush &brush) + { setData(column, Qt::ForegroundRole, brush); } + + inline Qt::CheckState checkState(int column) const + { return static_cast<Qt::CheckState>(data(column, Qt::CheckStateRole).toInt()); } + inline void setCheckState(int column, Qt::CheckState state) + { setData(column, Qt::CheckStateRole, state); } + + inline QSize sizeHint(int column) const + { return qvariant_cast<QSize>(data(column, Qt::SizeHintRole)); } + inline void setSizeHint(int column, const QSize &size) + { setData(column, Qt::SizeHintRole, size); } + + virtual QVariant data(int column, int role) const; + virtual void setData(int column, int role, const QVariant &value); + + virtual bool operator<(const QTreeWidgetItem &other) const; + +#ifndef QT_NO_DATASTREAM + virtual void read(QDataStream &in); + virtual void write(QDataStream &out) const; +#endif + QTreeWidgetItem &operator=(const QTreeWidgetItem &other); + + inline QTreeWidgetItem *parent() const { return par; } + inline QTreeWidgetItem *child(int index) const { + if (index < 0 || index >= children.size()) + return 0; + executePendingSort(); + return children.at(index); + } + inline int childCount() const { return children.count(); } + inline int columnCount() const { return values.count(); } + inline int indexOfChild(QTreeWidgetItem *child) const; + + void addChild(QTreeWidgetItem *child); + void insertChild(int index, QTreeWidgetItem *child); + void removeChild(QTreeWidgetItem *child); + QTreeWidgetItem *takeChild(int index); + + void addChildren(const QList<QTreeWidgetItem*> &children); + void insertChildren(int index, const QList<QTreeWidgetItem*> &children); + QList<QTreeWidgetItem*> takeChildren(); + + inline int type() const { return rtti; } + inline void sortChildren(int column, Qt::SortOrder order) + { sortChildren(column, order, false); } + +protected: + void emitDataChanged(); + +private: + void sortChildren(int column, Qt::SortOrder order, bool climb); + QVariant childrenCheckState(int column) const; + void itemChanged(); + void executePendingSort() const; + + int rtti; + // One item has a vector of column entries. Each column has a vector of (role, value) pairs. + QVector< QVector<QWidgetItemData> > values; + QTreeWidget *view; + QTreeWidgetItemPrivate *d; + QTreeWidgetItem *par; + QList<QTreeWidgetItem*> children; + Qt::ItemFlags itemFlags; +}; + +inline void QTreeWidgetItem::setText(int column, const QString &atext) +{ setData(column, Qt::DisplayRole, atext); } + +inline void QTreeWidgetItem::setIcon(int column, const QIcon &aicon) +{ setData(column, Qt::DecorationRole, aicon); } + +#ifndef QT_NO_STATUSTIP +inline void QTreeWidgetItem::setStatusTip(int column, const QString &astatusTip) +{ setData(column, Qt::StatusTipRole, astatusTip); } +#endif + +#ifndef QT_NO_TOOLTIP +inline void QTreeWidgetItem::setToolTip(int column, const QString &atoolTip) +{ setData(column, Qt::ToolTipRole, atoolTip); } +#endif + +#ifndef QT_NO_WHATSTHIS +inline void QTreeWidgetItem::setWhatsThis(int column, const QString &awhatsThis) +{ setData(column, Qt::WhatsThisRole, awhatsThis); } +#endif + +inline void QTreeWidgetItem::setFont(int column, const QFont &afont) +{ setData(column, Qt::FontRole, afont); } + +inline int QTreeWidgetItem::indexOfChild(QTreeWidgetItem *achild) const +{ executePendingSort(); return children.indexOf(achild); } + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &out, const QTreeWidgetItem &item); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &in, QTreeWidgetItem &item); +#endif + +class QTreeWidgetPrivate; + +class Q_GUI_EXPORT QTreeWidget : public QTreeView +{ + Q_OBJECT + Q_PROPERTY(int columnCount READ columnCount WRITE setColumnCount) + Q_PROPERTY(int topLevelItemCount READ topLevelItemCount) + + friend class QTreeModel; + friend class QTreeWidgetItem; +public: + explicit QTreeWidget(QWidget *parent = 0); + ~QTreeWidget(); + + int columnCount() const; + void setColumnCount(int columns); + + QTreeWidgetItem *invisibleRootItem() const; + QTreeWidgetItem *topLevelItem(int index) const; + int topLevelItemCount() const; + void insertTopLevelItem(int index, QTreeWidgetItem *item); + void addTopLevelItem(QTreeWidgetItem *item); + QTreeWidgetItem *takeTopLevelItem(int index); + int indexOfTopLevelItem(QTreeWidgetItem *item); // ### Qt 5: remove me + int indexOfTopLevelItem(QTreeWidgetItem *item) const; + + void insertTopLevelItems(int index, const QList<QTreeWidgetItem*> &items); + void addTopLevelItems(const QList<QTreeWidgetItem*> &items); + + QTreeWidgetItem *headerItem() const; + void setHeaderItem(QTreeWidgetItem *item); + void setHeaderLabels(const QStringList &labels); + inline void setHeaderLabel(const QString &label); + + QTreeWidgetItem *currentItem() const; + int currentColumn() const; + void setCurrentItem(QTreeWidgetItem *item); + void setCurrentItem(QTreeWidgetItem *item, int column); + void setCurrentItem(QTreeWidgetItem *item, int column, QItemSelectionModel::SelectionFlags command); + + QTreeWidgetItem *itemAt(const QPoint &p) const; + inline QTreeWidgetItem *itemAt(int x, int y) const; + QRect visualItemRect(const QTreeWidgetItem *item) const; + + int sortColumn() const; + void sortItems(int column, Qt::SortOrder order); + void setSortingEnabled(bool enable); + bool isSortingEnabled() const; + + void editItem(QTreeWidgetItem *item, int column = 0); + void openPersistentEditor(QTreeWidgetItem *item, int column = 0); + void closePersistentEditor(QTreeWidgetItem *item, int column = 0); + + QWidget *itemWidget(QTreeWidgetItem *item, int column) const; + void setItemWidget(QTreeWidgetItem *item, int column, QWidget *widget); + inline void removeItemWidget(QTreeWidgetItem *item, int column); + + bool isItemSelected(const QTreeWidgetItem *item) const; + void setItemSelected(const QTreeWidgetItem *item, bool select); + QList<QTreeWidgetItem*> selectedItems() const; + QList<QTreeWidgetItem*> findItems(const QString &text, Qt::MatchFlags flags, + int column = 0) const; + + bool isItemHidden(const QTreeWidgetItem *item) const; + void setItemHidden(const QTreeWidgetItem *item, bool hide); + + bool isItemExpanded(const QTreeWidgetItem *item) const; + void setItemExpanded(const QTreeWidgetItem *item, bool expand); + + bool isFirstItemColumnSpanned(const QTreeWidgetItem *item) const; + void setFirstItemColumnSpanned(const QTreeWidgetItem *item, bool span); + + QTreeWidgetItem *itemAbove(const QTreeWidgetItem *item) const; + QTreeWidgetItem *itemBelow(const QTreeWidgetItem *item) const; + + void setSelectionModel(QItemSelectionModel *selectionModel); + +public Q_SLOTS: + void scrollToItem(const QTreeWidgetItem *item, + QAbstractItemView::ScrollHint hint = EnsureVisible); + void expandItem(const QTreeWidgetItem *item); + void collapseItem(const QTreeWidgetItem *item); + void clear(); + +Q_SIGNALS: + void itemPressed(QTreeWidgetItem *item, int column); + void itemClicked(QTreeWidgetItem *item, int column); + void itemDoubleClicked(QTreeWidgetItem *item, int column); + void itemActivated(QTreeWidgetItem *item, int column); + void itemEntered(QTreeWidgetItem *item, int column); + void itemChanged(QTreeWidgetItem *item, int column); + void itemExpanded(QTreeWidgetItem *item); + void itemCollapsed(QTreeWidgetItem *item); + void currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous); + void itemSelectionChanged(); + +protected: + bool event(QEvent *e); + virtual QStringList mimeTypes() const; + virtual QMimeData *mimeData(const QList<QTreeWidgetItem*> items) const; + virtual bool dropMimeData(QTreeWidgetItem *parent, int index, + const QMimeData *data, Qt::DropAction action); + virtual Qt::DropActions supportedDropActions() const; + QList<QTreeWidgetItem*> items(const QMimeData *data) const; + + QModelIndex indexFromItem(QTreeWidgetItem *item, int column = 0) const; + QTreeWidgetItem *itemFromIndex(const QModelIndex &index) const; + void dropEvent(QDropEvent *event); + +private: + void setModel(QAbstractItemModel *model); + + Q_DECLARE_PRIVATE(QTreeWidget) + Q_DISABLE_COPY(QTreeWidget) + + Q_PRIVATE_SLOT(d_func(), void _q_emitItemPressed(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemClicked(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemDoubleClicked(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemActivated(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemEntered(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemChanged(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemExpanded(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemCollapsed(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitCurrentItemChanged(const QModelIndex &previous, const QModelIndex ¤t)) + Q_PRIVATE_SLOT(d_func(), void _q_sort()) + Q_PRIVATE_SLOT(d_func(), void _q_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)) + Q_PRIVATE_SLOT(d_func(), void _q_selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)) +}; + +inline void QTreeWidget::removeItemWidget(QTreeWidgetItem *item, int column) +{ setItemWidget(item, column, 0); } + +inline QTreeWidgetItem *QTreeWidget::itemAt(int ax, int ay) const +{ return itemAt(QPoint(ax, ay)); } + +inline void QTreeWidget::setHeaderLabel(const QString &alabel) +{ setHeaderLabels(QStringList(alabel)); } + +inline void QTreeWidgetItem::setSelected(bool aselect) +{ if (view) view->setItemSelected(this, aselect); } + +inline bool QTreeWidgetItem::isSelected() const +{ return (view ? view->isItemSelected(this) : false); } + +inline void QTreeWidgetItem::setHidden(bool ahide) +{ if (view) view->setItemHidden(this, ahide); } + +inline bool QTreeWidgetItem::isHidden() const +{ return (view ? view->isItemHidden(this) : false); } + +inline void QTreeWidgetItem::setExpanded(bool aexpand) +{ if (view) view->setItemExpanded(this, aexpand); } + +inline bool QTreeWidgetItem::isExpanded() const +{ return (view ? view->isItemExpanded(this) : false); } + +inline void QTreeWidgetItem::setFirstColumnSpanned(bool aspan) +{ if (view) view->setFirstItemColumnSpanned(this, aspan); } + +inline bool QTreeWidgetItem::isFirstColumnSpanned() const +{ return (view ? view->isFirstItemColumnSpanned(this) : false); } + +inline void QTreeWidgetItem::setDisabled(bool disabled) +{ setFlags(disabled ? (flags() & ~Qt::ItemIsEnabled) : flags() | Qt::ItemIsEnabled); } + +inline bool QTreeWidgetItem::isDisabled() const +{ return !(flags() & Qt::ItemIsEnabled); } + +#endif // QT_NO_TREEWIDGET + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTREEWIDGET_H diff --git a/src/gui/itemviews/qtreewidget_p.h b/src/gui/itemviews/qtreewidget_p.h new file mode 100644 index 0000000..a089cf5 --- /dev/null +++ b/src/gui/itemviews/qtreewidget_p.h @@ -0,0 +1,249 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTREEWIDGET_P_H +#define QTREEWIDGET_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. This header file may change +// from version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qabstractitemmodel.h> +#include <private/qabstractitemmodel_p.h> +#include <QtCore/qpair.h> +#include <QtCore/qbasictimer.h> +#include <QtGui/qtreewidget.h> +#include <private/qtreeview_p.h> +#include <QtGui/qheaderview.h> + +#ifndef QT_NO_TREEWIDGET + +QT_BEGIN_NAMESPACE + +class QTreeWidgetItem; +class QTreeWidgetItemIterator; +class QTreeModelPrivate; + +class QTreeModel : public QAbstractItemModel +{ + Q_OBJECT + friend class QTreeWidget; + friend class QTreeWidgetPrivate; + friend class QTreeWidgetItem; + friend class QTreeWidgetItemPrivate; + friend class QTreeWidgetItemIterator; + friend class QTreeWidgetItemIteratorPrivate; + +public: + explicit QTreeModel(int columns = 0, QTreeWidget *parent = 0); + ~QTreeModel(); + + inline QTreeWidget *view() const + { return qobject_cast<QTreeWidget*>(QObject::parent()); } + + void clear(); + void setColumnCount(int columns); + + QTreeWidgetItem *item(const QModelIndex &index) const; + void itemChanged(QTreeWidgetItem *item); + + QModelIndex index(const QTreeWidgetItem *item, int column) const; + QModelIndex index(int row, int column, const QModelIndex &parent) const; + QModelIndex parent(const QModelIndex &child) const; + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + bool hasChildren(const QModelIndex &parent) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex &index, const QVariant &value, int role); + + QMap<int, QVariant> itemData(const QModelIndex &index) const; + + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, + int role); + + Qt::ItemFlags flags(const QModelIndex &index) const; + + void sort(int column, Qt::SortOrder order); + void ensureSorted(int column, Qt::SortOrder order, + int start, int end, const QModelIndex &parent); + static bool itemLessThan(const QPair<QTreeWidgetItem*,int> &left, + const QPair<QTreeWidgetItem*,int> &right); + static bool itemGreaterThan(const QPair<QTreeWidgetItem*,int> &left, + const QPair<QTreeWidgetItem*,int> &right); + static QList<QTreeWidgetItem*>::iterator sortedInsertionIterator( + const QList<QTreeWidgetItem*>::iterator &begin, + const QList<QTreeWidgetItem*>::iterator &end, + Qt::SortOrder order, QTreeWidgetItem *item); + + bool insertRows(int row, int count, const QModelIndex &); + bool insertColumns(int column, int count, const QModelIndex &); + + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + + // dnd + QStringList mimeTypes() const; + QMimeData *mimeData(const QModelIndexList &indexes) const; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent); + Qt::DropActions supportedDropActions() const; + + QMimeData *internalMimeData() const; + + inline QModelIndex createIndexFromItem(int row, int col, QTreeWidgetItem *item) const + { return createIndex(row, col, item); } + +protected: + QTreeModel(QTreeModelPrivate &, QTreeWidget *parent = 0); + void emitDataChanged(QTreeWidgetItem *item, int column); + void beginInsertItems(QTreeWidgetItem *parent, int row, int count); + void endInsertItems(); + void beginRemoveItems(QTreeWidgetItem *parent, int row, int count); + void endRemoveItems(); + void sortItems(QList<QTreeWidgetItem*> *items, int column, Qt::SortOrder order); + void timerEvent(QTimerEvent *); + +private: + QTreeWidgetItem *rootItem; + QTreeWidgetItem *headerItem; + + mutable QModelIndexList cachedIndexes; + QList<QTreeWidgetItemIterator*> iterators; + + mutable QBasicTimer sortPendingTimer; + mutable bool skipPendingSort; //while doing internal operation we don't care about sorting + bool inline executePendingSort() const; + + bool isChanging() const; + +private: + Q_DECLARE_PRIVATE(QTreeModel) +public: + struct SkipSorting + { + const QTreeModel * const model; + const bool previous; + SkipSorting(const QTreeModel *m) : model(m), previous(model->skipPendingSort) + { model->skipPendingSort = true; } + ~SkipSorting() { model->skipPendingSort = previous; } + }; + friend struct SkipSorting; +}; + +QT_BEGIN_INCLUDE_NAMESPACE +#include "private/qabstractitemmodel_p.h" +QT_END_INCLUDE_NAMESPACE + +class QTreeModelPrivate : public QAbstractItemModelPrivate +{ + Q_DECLARE_PUBLIC(QTreeModel) +}; + +class QTreeWidgetItemPrivate +{ +public: + QTreeWidgetItemPrivate(QTreeWidgetItem *item) + : q(item), disabled(false), selected(false), rowGuess(-1), policy(QTreeWidgetItem::DontShowIndicatorWhenChildless) {} + void propagateDisabled(QTreeWidgetItem *item); + void sortChildren(int column, Qt::SortOrder order, bool climb); + QTreeWidgetItem *q; + QVariantList display; + uint disabled : 1; + uint selected : 1; + int rowGuess; + QTreeWidgetItem::ChildIndicatorPolicy policy; +}; + + +inline bool QTreeModel::executePendingSort() const +{ + if (!skipPendingSort && sortPendingTimer.isActive() && !isChanging()) { + sortPendingTimer.stop(); + int column = view()->header()->sortIndicatorSection(); + Qt::SortOrder order = view()->header()->sortIndicatorOrder(); + QTreeModel *that = const_cast<QTreeModel*>(this); + that->sort(column, order); + return true; + } + return false; +} + +class QTreeWidgetPrivate : public QTreeViewPrivate +{ + friend class QTreeModel; + Q_DECLARE_PUBLIC(QTreeWidget) +public: + QTreeWidgetPrivate() : QTreeViewPrivate(), explicitSortColumn(-1) {} + inline QTreeModel *model() const + { return qobject_cast<QTreeModel*>(q_func()->model()); } + inline QModelIndex index(const QTreeWidgetItem *item, int column = 0) const + { return model()->index(item, column); } + inline QTreeWidgetItem *item(const QModelIndex &index) const + { return model()->item(index); } + void _q_emitItemPressed(const QModelIndex &index); + void _q_emitItemClicked(const QModelIndex &index); + void _q_emitItemDoubleClicked(const QModelIndex &index); + void _q_emitItemActivated(const QModelIndex &index); + void _q_emitItemEntered(const QModelIndex &index); + void _q_emitItemChanged(const QModelIndex &index); + void _q_emitItemExpanded(const QModelIndex &index); + void _q_emitItemCollapsed(const QModelIndex &index); + void _q_emitCurrentItemChanged(const QModelIndex &previous, const QModelIndex &index); + void _q_sort(); + void _q_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void _q_selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + + // used by QTreeWidgetItem::sortChildren to make sure the column argument is used + int explicitSortColumn; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_TREEWIDGET + +#endif // QTREEWIDGET_P_H diff --git a/src/gui/itemviews/qtreewidgetitemiterator.cpp b/src/gui/itemviews/qtreewidgetitemiterator.cpp new file mode 100644 index 0000000..3e30e03 --- /dev/null +++ b/src/gui/itemviews/qtreewidgetitemiterator.cpp @@ -0,0 +1,459 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qtreewidgetitemiterator_p.h> +#include "qtreewidget.h" +#include "qtreewidget_p.h" +#include "qwidgetitemdata_p.h" + +#ifndef QT_NO_TREEWIDGET + +QT_BEGIN_NAMESPACE + +/*! + \class QTreeWidgetItemIterator + \ingroup model-view + \brief The QTreeWidgetItemIterator class provides a way to iterate over the + items in a QTreeWidget instance. + + The iterator will walk the items in a pre-order traversal order, thus visiting the + parent node \e before it continues to the child nodes. + + For example, the following code examples each item in a tree, checking the + text in the first column against a user-specified search string: + + \snippet doc/src/snippets/qtreewidgetitemiterator-using/mainwindow.cpp 0 + + It is also possible to filter out certain types of node by passing certain + \l{IteratorFlag}{flags} to the constructor of QTreeWidgetItemIterator. + + \sa QTreeWidget, {Model/View Programming}, QTreeWidgetItem +*/ + +/*! + Constructs an iterator for the same QTreeWidget as \a it. The + current iterator item is set to point on the current item of \a it. +*/ + +QTreeWidgetItemIterator::QTreeWidgetItemIterator(const QTreeWidgetItemIterator &it) + : d_ptr(new QTreeWidgetItemIteratorPrivate(*(it.d_ptr))), + current(it.current), flags(it.flags) +{ + Q_D(QTreeWidgetItemIterator); + Q_ASSERT(d->m_model); + d->m_model->iterators.append(this); +} + +/*! + Constructs an iterator for the given \a widget that uses the specified \a flags + to determine which items are found during iteration. + The iterator is set to point to the first top-level item contained in the widget, + or the next matching item if the top-level item doesn't match the flags. + + \sa QTreeWidgetItemIterator::IteratorFlag +*/ + +QTreeWidgetItemIterator::QTreeWidgetItemIterator(QTreeWidget *widget, IteratorFlags flags) +: current(0), flags(flags) +{ + Q_ASSERT(widget); + QTreeModel *model = qobject_cast<QTreeModel*>(widget->model()); + Q_ASSERT(model); + d_ptr = new QTreeWidgetItemIteratorPrivate(this, model); + model->iterators.append(this); + if (!model->rootItem->children.isEmpty()) current = model->rootItem->children.first(); + if (current && !matchesFlags(current)) + ++(*this); +} + +/*! + Constructs an iterator for the given \a item that uses the specified \a flags + to determine which items are found during iteration. + The iterator is set to point to \a item, or the next matching item if \a item + doesn't match the flags. + + \sa QTreeWidgetItemIterator::IteratorFlag +*/ + +QTreeWidgetItemIterator::QTreeWidgetItemIterator(QTreeWidgetItem *item, IteratorFlags flags) + : d_ptr(new QTreeWidgetItemIteratorPrivate( + this, qobject_cast<QTreeModel*>(item->view->model()))), + current(item), flags(flags) +{ + Q_D(QTreeWidgetItemIterator); + Q_ASSERT(item); + QTreeModel *model = qobject_cast<QTreeModel*>(item->view->model()); + Q_ASSERT(model); + model->iterators.append(this); + + // Initialize m_currentIndex and m_parentIndex as it would be if we had traversed from + // the beginning. + QTreeWidgetItem *parent = item; + parent = parent->parent(); + QList<QTreeWidgetItem *> children = parent ? parent->children : d->m_model->rootItem->children; + d->m_currentIndex = children.indexOf(item); + + while (parent) { + QTreeWidgetItem *itm = parent; + parent = parent->parent(); + QList<QTreeWidgetItem *> children = parent ? parent->children : d->m_model->rootItem->children; + int index = children.indexOf(itm); + d->m_parentIndex.prepend(index); + } + + if (current && !matchesFlags(current)) + ++(*this); +} + +/*! + Destroys the iterator. +*/ + +QTreeWidgetItemIterator::~QTreeWidgetItemIterator() +{ + d_func()->m_model->iterators.removeAll(this); + delete d_ptr; +} + +/*! + Assignment. Makes a copy of \a it and returns a reference to its + iterator. +*/ + +QTreeWidgetItemIterator &QTreeWidgetItemIterator::operator=(const QTreeWidgetItemIterator &it) +{ + Q_D(QTreeWidgetItemIterator); + if (d_func()->m_model != it.d_func()->m_model) { + d_func()->m_model->iterators.removeAll(this); + it.d_func()->m_model->iterators.append(this); + } + current = it.current; + flags = it.flags; + d->operator=(*it.d_func()); + return *this; +} + +/*! + The prefix ++ operator (++it) advances the iterator to the next matching item + and returns a reference to the resulting iterator. + Sets the current pointer to 0 if the current item is the last matching item. +*/ + +QTreeWidgetItemIterator &QTreeWidgetItemIterator::operator++() +{ + if (current) + do { + current = d_func()->next(current); + } while (current && !matchesFlags(current)); + return *this; +} + +/*! + The prefix -- operator (--it) advances the iterator to the previous matching item + and returns a reference to the resulting iterator. + Sets the current pointer to 0 if the current item is the first matching item. +*/ + +QTreeWidgetItemIterator &QTreeWidgetItemIterator::operator--() +{ + if (current) + do { + current = d_func()->previous(current); + } while (current && !matchesFlags(current)); + return *this; +} + +/*! + \internal +*/ +bool QTreeWidgetItemIterator::matchesFlags(const QTreeWidgetItem *item) const +{ + if (!item) + return false; + + if (flags == All) + return true; + + { + Qt::ItemFlags itemFlags = item->flags(); + if ((flags & Selectable) && !(itemFlags & Qt::ItemIsSelectable)) + return false; + if ((flags & NotSelectable) && (itemFlags & Qt::ItemIsSelectable)) + return false; + if ((flags & DragEnabled) && !(itemFlags & Qt::ItemIsDragEnabled)) + return false; + if ((flags & DragDisabled) && (itemFlags & Qt::ItemIsDragEnabled)) + return false; + if ((flags & DropEnabled) && !(itemFlags & Qt::ItemIsDropEnabled)) + return false; + if ((flags & DropDisabled) && (itemFlags & Qt::ItemIsDropEnabled)) + return false; + if ((flags & Enabled) && !(itemFlags & Qt::ItemIsEnabled)) + return false; + if ((flags & Disabled) && (itemFlags & Qt::ItemIsEnabled)) + return false; + if ((flags & Editable) && !(itemFlags & Qt::ItemIsEditable)) + return false; + if ((flags & NotEditable) && (itemFlags & Qt::ItemIsEditable)) + return false; + } + + if (flags & (Checked|NotChecked)) { + // ### We only test the check state for column 0 + Qt::CheckState check = item->checkState(0); + // PartiallyChecked matches as Checked. + if ((flags & Checked) && (check == Qt::Unchecked)) + return false; + if ((flags & NotChecked) && (check != Qt::Unchecked)) + return false; + } + + if ((flags & HasChildren) && !item->childCount()) + return false; + if ((flags & NoChildren) && item->childCount()) + return false; + + if ((flags & Hidden) && !item->isHidden()) + return false; + if ((flags & NotHidden) && item->isHidden()) + return false; + + if ((flags & Selected) && !item->isSelected()) + return false; + if ((flags & Unselected) && item->isSelected()) + return false; + + return true; +} + +/* + * Implementation of QTreeWidgetItemIteratorPrivate + */ +QTreeWidgetItem* QTreeWidgetItemIteratorPrivate::nextSibling(const QTreeWidgetItem* item) const +{ + Q_ASSERT(item); + QTreeWidgetItem *next = 0; + if (QTreeWidgetItem *par = item->parent()) { + int i = par->indexOfChild(const_cast<QTreeWidgetItem*>(item)); + next = par->child(i + 1); + } else { + QTreeWidget *tw = item->treeWidget(); + int i = tw->indexOfTopLevelItem(const_cast<QTreeWidgetItem*>(item)); + next = tw->topLevelItem(i + 1); + } + return next; +} + +QTreeWidgetItem *QTreeWidgetItemIteratorPrivate::next(const QTreeWidgetItem *current) +{ + if (!current) return 0; + + QTreeWidgetItem *next = 0; + if (current->childCount()) { + // walk the child + m_parentIndex.push(m_currentIndex); + m_currentIndex = 0; + next = current->child(0); + } else { + // walk the sibling + QTreeWidgetItem *parent = current->parent(); + next = parent ? parent->child(m_currentIndex + 1) + : m_model->rootItem->child(m_currentIndex + 1); + while (!next && parent) { + // if we had no sibling walk up the parent and try the sibling of that + parent = parent->parent(); + m_currentIndex = m_parentIndex.pop(); + next = parent ? parent->child(m_currentIndex + 1) + : m_model->rootItem->child(m_currentIndex + 1); + } + if (next) ++(m_currentIndex); + } + return next; +} + +QTreeWidgetItem *QTreeWidgetItemIteratorPrivate::previous(const QTreeWidgetItem *current) +{ + if (!current) return 0; + + QTreeWidgetItem *prev = 0; + // walk the previous sibling + QTreeWidgetItem *parent = current->parent(); + prev = parent ? parent->child(m_currentIndex - 1) + : m_model->rootItem->child(m_currentIndex - 1); + if (prev) { + // Yes, we had a previous sibling but we need go down to the last leafnode. + --m_currentIndex; + while (prev && prev->childCount()) { + m_parentIndex.push(m_currentIndex); + m_currentIndex = prev->childCount() - 1; + prev = prev->child(m_currentIndex); + } + } else if (parent) { + m_currentIndex = m_parentIndex.pop(); + prev = parent; + } + return prev; +} + +void QTreeWidgetItemIteratorPrivate::ensureValidIterator(const QTreeWidgetItem *itemToBeRemoved) +{ + Q_Q(QTreeWidgetItemIterator); + Q_ASSERT(itemToBeRemoved); + + if (!q->current) return; + QTreeWidgetItem *nextItem = q->current; + + // Do not walk to the ancestor to find the other item if they have the same parent. + if (nextItem->parent() != itemToBeRemoved->parent()) { + while (nextItem->parent() && nextItem != itemToBeRemoved) { + nextItem = nextItem->parent(); + } + } + // If the item to be removed is an ancestor of the current iterator item, + // we need to adjust the iterator. + if (nextItem == itemToBeRemoved) { + QTreeWidgetItem *parent = nextItem; + nextItem = 0; + while (parent && !nextItem) { + nextItem = nextSibling(parent); + parent = parent->parent(); + } + if (nextItem) { + // Ooooh... Set the iterator to the next valid item + *q = QTreeWidgetItemIterator(nextItem, q->flags); + if (!(q->matchesFlags(nextItem))) ++(*q); + } else { + // set it to null. + q->current = 0; + m_parentIndex.clear(); + return; + } + } + if (nextItem->parent() == itemToBeRemoved->parent()) { + // They have the same parent, i.e. we have to adjust the m_currentIndex member of the iterator + // if the deleted item is to the left of the nextItem. + + QTreeWidgetItem *par = itemToBeRemoved->parent(); // We know they both have the same parent. + QTreeWidget *tw = itemToBeRemoved->treeWidget(); // ..and widget + int indexOfItemToBeRemoved = par ? par->indexOfChild(const_cast<QTreeWidgetItem *>(itemToBeRemoved)) + : tw->indexOfTopLevelItem(const_cast<QTreeWidgetItem *>(itemToBeRemoved)); + int indexOfNextItem = par ? par->indexOfChild(nextItem) : tw->indexOfTopLevelItem(nextItem); + + if (indexOfItemToBeRemoved <= indexOfNextItem) { + // A sibling to the left of us was deleted, adjust the m_currentIndex member of the iterator. + // Note that the m_currentIndex will be wrong until the item is actually removed! + m_currentIndex--; + } + } +} + +/*! + \fn const QTreeWidgetItemIterator QTreeWidgetItemIterator::operator++(int) + + The postfix ++ operator (it++) advances the iterator to the next matching item + and returns an iterator to the previously current item. +*/ + +/*! + \fn QTreeWidgetItemIterator &QTreeWidgetItemIterator::operator+=(int n) + + Makes the iterator go forward by \a n matching items. (If n is negative, the + iterator goes backward.) + + If the current item is beyond the last item, the current item pointer is + set to 0. Returns the resulting iterator. +*/ + +/*! + \fn const QTreeWidgetItemIterator QTreeWidgetItemIterator::operator--(int) + + The postfix -- operator (it--) makes the preceding matching item current and returns an iterator to the previously current item. +*/ + +/*! + \fn QTreeWidgetItemIterator &QTreeWidgetItemIterator::operator-=(int n) + + Makes the iterator go backward by \a n matching items. (If n is negative, the + iterator goes forward.) + + If the current item is ahead of the last item, the current item pointer is + set to 0. Returns the resulting iterator. +*/ + +/*! + \fn QTreeWidgetItem *QTreeWidgetItemIterator::operator*() const + + Dereference operator. Returns a pointer to the current item. +*/ + + +/*! + \enum QTreeWidgetItemIterator::IteratorFlag + + These flags can be passed to a QTreeWidgetItemIterator constructor + (OR-ed together if more than one is used), so that the iterator + will only iterate over items that match the given flags. + + \value All + \value Hidden + \value NotHidden + \value Selected + \value Unselected + \value Selectable + \value NotSelectable + \value DragEnabled + \value DragDisabled + \value DropEnabled + \value DropDisabled + \value HasChildren + \value NoChildren + \value Checked + \value NotChecked + \value Enabled + \value Disabled + \value Editable + \value NotEditable + \value UserFlag +*/ + +QT_END_NAMESPACE + +#endif // QT_NO_TREEWIDGET diff --git a/src/gui/itemviews/qtreewidgetitemiterator.h b/src/gui/itemviews/qtreewidgetitemiterator.h new file mode 100644 index 0000000..8a76c69 --- /dev/null +++ b/src/gui/itemviews/qtreewidgetitemiterator.h @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTREEWIDGETITEMITERATOR_H +#define QTREEWIDGETITEMITERATOR_H + +#include <QtCore/qglobal.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_TREEWIDGET + +class QTreeWidget; +class QTreeWidgetItem; +class QTreeModel; + +class QTreeWidgetItemIteratorPrivate; +class Q_GUI_EXPORT QTreeWidgetItemIterator +{ + friend class QTreeModel; + +public: + enum IteratorFlag { + All = 0x00000000, + Hidden = 0x00000001, + NotHidden = 0x00000002, + Selected = 0x00000004, + Unselected = 0x00000008, + Selectable = 0x00000010, + NotSelectable = 0x00000020, + DragEnabled = 0x00000040, + DragDisabled = 0x00000080, + DropEnabled = 0x00000100, + DropDisabled = 0x00000200, + HasChildren = 0x00000400, + NoChildren = 0x00000800, + Checked = 0x00001000, + NotChecked = 0x00002000, + Enabled = 0x00004000, + Disabled = 0x00008000, + Editable = 0x00010000, + NotEditable = 0x00020000, + UserFlag = 0x01000000 // The first flag that can be used by the user. + }; + Q_DECLARE_FLAGS(IteratorFlags, IteratorFlag) + + QTreeWidgetItemIterator(const QTreeWidgetItemIterator &it); + explicit QTreeWidgetItemIterator(QTreeWidget *widget, IteratorFlags flags = All); + explicit QTreeWidgetItemIterator(QTreeWidgetItem *item, IteratorFlags flags = All); + ~QTreeWidgetItemIterator(); + + QTreeWidgetItemIterator &operator=(const QTreeWidgetItemIterator &it); + + QTreeWidgetItemIterator &operator++(); + inline const QTreeWidgetItemIterator operator++(int); + inline QTreeWidgetItemIterator &operator+=(int n); + + QTreeWidgetItemIterator &operator--(); + inline const QTreeWidgetItemIterator operator--(int); + inline QTreeWidgetItemIterator &operator-=(int n); + + inline QTreeWidgetItem *operator*() const; + +private: + bool matchesFlags(const QTreeWidgetItem *item) const; + QTreeWidgetItemIteratorPrivate *d_ptr; + QTreeWidgetItem *current; + IteratorFlags flags; + Q_DECLARE_PRIVATE(QTreeWidgetItemIterator) +}; + +inline const QTreeWidgetItemIterator QTreeWidgetItemIterator::operator++(int) +{ + QTreeWidgetItemIterator it = *this; + ++(*this); + return it; +} + +inline const QTreeWidgetItemIterator QTreeWidgetItemIterator::operator--(int) +{ + QTreeWidgetItemIterator it = *this; + --(*this); + return it; +} + +inline QTreeWidgetItemIterator &QTreeWidgetItemIterator::operator+=(int n) +{ + if (n < 0) + return (*this) -= (-n); + while (current && n--) + ++(*this); + return *this; +} + +inline QTreeWidgetItemIterator &QTreeWidgetItemIterator::operator-=(int n) +{ + if (n < 0) + return (*this) += (-n); + while (current && n--) + --(*this); + return *this; +} + +inline QTreeWidgetItem *QTreeWidgetItemIterator::operator*() const +{ + return current; +} + +Q_DECLARE_OPERATORS_FOR_FLAGS(QTreeWidgetItemIterator::IteratorFlags) + + +QT_END_NAMESPACE +#endif // QT_NO_TREEWIDGET +QT_END_HEADER + +#endif // QTREEWIDGETITEMITERATOR_H diff --git a/src/gui/itemviews/qtreewidgetitemiterator_p.h b/src/gui/itemviews/qtreewidgetitemiterator_p.h new file mode 100644 index 0000000..cf800ec --- /dev/null +++ b/src/gui/itemviews/qtreewidgetitemiterator_p.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTREEWIDGETITEMITERATOR_P_H +#define QTREEWIDGETITEMITERATOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qstack.h> + +#ifndef QT_NO_TREEWIDGET +#include "qtreewidgetitemiterator.h" + +QT_BEGIN_NAMESPACE + +class QTreeModel; +class QTreeWidgetItem; + +class QTreeWidgetItemIteratorPrivate { + Q_DECLARE_PUBLIC(QTreeWidgetItemIterator) +public: + QTreeWidgetItemIteratorPrivate(QTreeWidgetItemIterator *q, QTreeModel *model) + : m_currentIndex(0), m_model(model), q_ptr(q) + { + + } + + QTreeWidgetItemIteratorPrivate(const QTreeWidgetItemIteratorPrivate& other) + : m_currentIndex(other.m_currentIndex), m_model(other.m_model), m_parentIndex(other.m_parentIndex) + { + + } + + QTreeWidgetItemIteratorPrivate &operator=(const QTreeWidgetItemIteratorPrivate& other) + { + m_currentIndex = other.m_currentIndex; + m_parentIndex = other.m_parentIndex; + m_model = other.m_model; + return (*this); + } + + ~QTreeWidgetItemIteratorPrivate() + { + } + + QTreeWidgetItem* nextSibling(const QTreeWidgetItem* item) const; + void ensureValidIterator(const QTreeWidgetItem *itemToBeRemoved); + + QTreeWidgetItem *next(const QTreeWidgetItem *current); + QTreeWidgetItem *previous(const QTreeWidgetItem *current); +private: + int m_currentIndex; + QTreeModel *m_model; // This iterator class should not have ownership of the model. + QStack<int> m_parentIndex; + QTreeWidgetItemIterator *q_ptr; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_TREEWIDGET + +#endif //QTREEWIDGETITEMITERATOR_P_H diff --git a/src/gui/itemviews/qwidgetitemdata_p.h b/src/gui/itemviews/qwidgetitemdata_p.h new file mode 100644 index 0000000..32297fb --- /dev/null +++ b/src/gui/itemviews/qwidgetitemdata_p.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWIDGETITEMDATA_P_H +#define QWIDGETITEMDATA_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +QT_BEGIN_NAMESPACE + +class QWidgetItemData +{ +public: + inline QWidgetItemData() : role(-1) {} + inline QWidgetItemData(int r, QVariant v) : role(r), value(v) {} + int role; + QVariant value; + inline bool operator==(const QWidgetItemData &other) { return role == other.role && value == other.value; } +}; + +#ifndef QT_NO_DATASTREAM + +inline QDataStream &operator>>(QDataStream &in, QWidgetItemData &data) +{ + in >> data.role; + in >> data.value; + return in; +} + +inline QDataStream &operator<<(QDataStream &out, const QWidgetItemData &data) +{ + out << data.role; + out << data.value; + return out; +} + +#endif // QT_NO_DATASTREAM + +QT_END_NAMESPACE + +#endif // QWIDGETITEMDATA_P_H |