summaryrefslogtreecommitdiffstats
path: root/src/widgets/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/widgets/util')
-rw-r--r--src/widgets/util/qcompleter.cpp1833
-rw-r--r--src/widgets/util/qcompleter.h171
-rw-r--r--src/widgets/util/qcompleter_p.h264
-rw-r--r--src/widgets/util/qflickgesture.cpp715
-rw-r--r--src/widgets/util/qflickgesture_p.h113
-rw-r--r--src/widgets/util/qscroller.cpp2056
-rw-r--r--src/widgets/util/qscroller.h155
-rw-r--r--src/widgets/util/qscroller_mac.mm75
-rw-r--r--src/widgets/util/qscroller_p.h209
-rw-r--r--src/widgets/util/qscrollerproperties.cpp393
-rw-r--r--src/widgets/util/qscrollerproperties.h140
-rw-r--r--src/widgets/util/qscrollerproperties_p.h94
-rw-r--r--src/widgets/util/qsystemtrayicon.cpp674
-rw-r--r--src/widgets/util/qsystemtrayicon.h132
-rw-r--r--src/widgets/util/qsystemtrayicon_mac.mm578
-rw-r--r--src/widgets/util/qsystemtrayicon_p.h186
-rw-r--r--src/widgets/util/qsystemtrayicon_qpa.cpp96
-rw-r--r--src/widgets/util/qsystemtrayicon_win.cpp524
-rw-r--r--src/widgets/util/qsystemtrayicon_wince.cpp293
-rw-r--r--src/widgets/util/qsystemtrayicon_x11.cpp401
-rw-r--r--src/widgets/util/qundogroup.cpp505
-rw-r--r--src/widgets/util/qundogroup.h110
-rw-r--r--src/widgets/util/qundostack.cpp1175
-rw-r--r--src/widgets/util/qundostack.h159
-rw-r--r--src/widgets/util/qundostack_p.h114
-rw-r--r--src/widgets/util/qundoview.cpp476
-rw-r--r--src/widgets/util/qundoview.h102
-rw-r--r--src/widgets/util/util.pri69
28 files changed, 11812 insertions, 0 deletions
diff --git a/src/widgets/util/qcompleter.cpp b/src/widgets/util/qcompleter.cpp
new file mode 100644
index 0000000000..d1ec03f1e4
--- /dev/null
+++ b/src/widgets/util/qcompleter.cpp
@@ -0,0 +1,1833 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \class QCompleter
+ \brief The QCompleter class provides completions based on an item model.
+ \since 4.2
+
+ You can use QCompleter to provide auto completions in any Qt
+ widget, such as QLineEdit and QComboBox.
+ When the user starts typing a word, QCompleter suggests possible ways of
+ completing the word, based on a word list. The word list is
+ provided as a QAbstractItemModel. (For simple applications, where
+ the word list is static, you can pass a QStringList to
+ QCompleter's constructor.)
+
+ \tableofcontents
+
+ \section1 Basic Usage
+
+ A QCompleter is used typically with a QLineEdit or QComboBox.
+ For example, here's how to provide auto completions from a simple
+ word list in a QLineEdit:
+
+ \snippet doc/src/snippets/code/src_gui_util_qcompleter.cpp 0
+
+ A QFileSystemModel can be used to provide auto completion of file names.
+ For example:
+
+ \snippet doc/src/snippets/code/src_gui_util_qcompleter.cpp 1
+
+ To set the model on which QCompleter should operate, call
+ setModel(). By default, QCompleter will attempt to match the \l
+ {completionPrefix}{completion prefix} (i.e., the word that the
+ user has started typing) against the Qt::EditRole data stored in
+ column 0 in the model case sensitively. This can be changed
+ using setCompletionRole(), setCompletionColumn(), and
+ setCaseSensitivity().
+
+ If the model is sorted on the column and role that are used for completion,
+ you can call setModelSorting() with either
+ QCompleter::CaseSensitivelySortedModel or
+ QCompleter::CaseInsensitivelySortedModel as the argument. On large models,
+ this can lead to significant performance improvements, because QCompleter
+ can then use binary search instead of linear search.
+
+ The model can be a \l{QAbstractListModel}{list model},
+ a \l{QAbstractTableModel}{table model}, or a
+ \l{QAbstractItemModel}{tree model}. Completion on tree models
+ is slightly more involved and is covered in the \l{Handling
+ Tree Models} section below.
+
+ The completionMode() determines the mode used to provide completions to
+ the user.
+
+ \section1 Iterating Through Completions
+
+ To retrieve a single candidate string, call setCompletionPrefix()
+ with the text that needs to be completed and call
+ currentCompletion(). You can iterate through the list of
+ completions as below:
+
+ \snippet doc/src/snippets/code/src_gui_util_qcompleter.cpp 2
+
+ completionCount() returns the total number of completions for the
+ current prefix. completionCount() should be avoided when possible,
+ since it requires a scan of the entire model.
+
+ \section1 The Completion Model
+
+ completionModel() return a list model that contains all possible
+ completions for the current completion prefix, in the order in which
+ they appear in the model. This model can be used to display the current
+ completions in a custom view. Calling setCompletionPrefix() automatically
+ refreshes the completion model.
+
+ \section1 Handling Tree Models
+
+ QCompleter can look for completions in tree models, assuming
+ that any item (or sub-item or sub-sub-item) can be unambiguously
+ represented as a string by specifying the path to the item. The
+ completion is then performed one level at a time.
+
+ Let's take the example of a user typing in a file system path.
+ The model is a (hierarchical) QFileSystemModel. The completion
+ occurs for every element in the path. For example, if the current
+ text is \c C:\Wind, QCompleter might suggest \c Windows to
+ complete the current path element. Similarly, if the current text
+ is \c C:\Windows\Sy, QCompleter might suggest \c System.
+
+ For this kind of completion to work, QCompleter needs to be able to
+ split the path into a list of strings that are matched at each level.
+ For \c C:\Windows\Sy, it needs to be split as "C:", "Windows" and "Sy".
+ The default implementation of splitPath(), splits the completionPrefix
+ using QDir::separator() if the model is a QFileSystemModel.
+
+ To provide completions, QCompleter needs to know the path from an index.
+ This is provided by pathFromIndex(). The default implementation of
+ pathFromIndex(), returns the data for the \l{Qt::EditRole}{edit role}
+ for list models and the absolute file path if the mode is a QFileSystemModel.
+
+ \sa QAbstractItemModel, QLineEdit, QComboBox, {Completer Example}
+*/
+
+#include "qcompleter_p.h"
+
+#ifndef QT_NO_COMPLETER
+
+#include "QtWidgets/qscrollbar.h"
+#include "QtWidgets/qstringlistmodel.h"
+#include "QtWidgets/qdirmodel.h"
+#include "QtWidgets/qfilesystemmodel.h"
+#include "QtWidgets/qheaderview.h"
+#include "QtWidgets/qlistview.h"
+#include "QtWidgets/qapplication.h"
+#include "QtGui/qevent.h"
+#include "QtWidgets/qheaderview.h"
+#include "QtWidgets/qdesktopwidget.h"
+#include "QtWidgets/qlineedit.h"
+
+QT_BEGIN_NAMESPACE
+
+QCompletionModel::QCompletionModel(QCompleterPrivate *c, QObject *parent)
+ : QAbstractProxyModel(*new QCompletionModelPrivate, parent),
+ c(c), showAll(false)
+{
+ createEngine();
+}
+
+int QCompletionModel::columnCount(const QModelIndex &) const
+{
+ Q_D(const QCompletionModel);
+ return d->model->columnCount();
+}
+
+void QCompletionModel::setSourceModel(QAbstractItemModel *source)
+{
+ bool hadModel = (sourceModel() != 0);
+
+ if (hadModel)
+ QObject::disconnect(sourceModel(), 0, this, 0);
+
+ QAbstractProxyModel::setSourceModel(source);
+
+ if (source) {
+ // TODO: Optimize updates in the source model
+ connect(source, SIGNAL(modelReset()), this, SLOT(invalidate()));
+ connect(source, SIGNAL(destroyed()), this, SLOT(modelDestroyed()));
+ connect(source, SIGNAL(layoutChanged()), this, SLOT(invalidate()));
+ connect(source, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted()));
+ connect(source, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(invalidate()));
+ connect(source, SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(invalidate()));
+ connect(source, SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(invalidate()));
+ connect(source, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(invalidate()));
+ }
+
+ invalidate();
+}
+
+void QCompletionModel::createEngine()
+{
+ bool sortedEngine = false;
+ switch (c->sorting) {
+ case QCompleter::UnsortedModel:
+ sortedEngine = false;
+ break;
+ case QCompleter::CaseSensitivelySortedModel:
+ sortedEngine = c->cs == Qt::CaseSensitive;
+ break;
+ case QCompleter::CaseInsensitivelySortedModel:
+ sortedEngine = c->cs == Qt::CaseInsensitive;
+ break;
+ }
+
+ if (sortedEngine)
+ engine.reset(new QSortedModelEngine(c));
+ else
+ engine.reset(new QUnsortedModelEngine(c));
+}
+
+QModelIndex QCompletionModel::mapToSource(const QModelIndex& index) const
+{
+ Q_D(const QCompletionModel);
+ if (!index.isValid())
+ return engine->curParent;
+
+ int row;
+ QModelIndex parent = engine->curParent;
+ if (!showAll) {
+ if (!engine->matchCount())
+ return QModelIndex();
+ Q_ASSERT(index.row() < engine->matchCount());
+ QIndexMapper& rootIndices = engine->historyMatch.indices;
+ if (index.row() < rootIndices.count()) {
+ row = rootIndices[index.row()];
+ parent = QModelIndex();
+ } else {
+ row = engine->curMatch.indices[index.row() - rootIndices.count()];
+ }
+ } else {
+ row = index.row();
+ }
+
+ return d->model->index(row, index.column(), parent);
+}
+
+QModelIndex QCompletionModel::mapFromSource(const QModelIndex& idx) const
+{
+ if (!idx.isValid())
+ return QModelIndex();
+
+ int row = -1;
+ if (!showAll) {
+ if (!engine->matchCount())
+ return QModelIndex();
+
+ QIndexMapper& rootIndices = engine->historyMatch.indices;
+ if (idx.parent().isValid()) {
+ if (idx.parent() != engine->curParent)
+ return QModelIndex();
+ } else {
+ row = rootIndices.indexOf(idx.row());
+ if (row == -1 && engine->curParent.isValid())
+ return QModelIndex(); // source parent and our parent don't match
+ }
+
+ if (row == -1) {
+ QIndexMapper& indices = engine->curMatch.indices;
+ engine->filterOnDemand(idx.row() - indices.last());
+ row = indices.indexOf(idx.row()) + rootIndices.count();
+ }
+
+ if (row == -1)
+ return QModelIndex();
+ } else {
+ if (idx.parent() != engine->curParent)
+ return QModelIndex();
+ row = idx.row();
+ }
+
+ return createIndex(row, idx.column());
+}
+
+bool QCompletionModel::setCurrentRow(int row)
+{
+ if (row < 0 || !engine->matchCount())
+ return false;
+
+ if (row >= engine->matchCount())
+ engine->filterOnDemand(row + 1 - engine->matchCount());
+
+ if (row >= engine->matchCount()) // invalid row
+ return false;
+
+ engine->curRow = row;
+ return true;
+}
+
+QModelIndex QCompletionModel::currentIndex(bool sourceIndex) const
+{
+ if (!engine->matchCount())
+ return QModelIndex();
+
+ int row = engine->curRow;
+ if (showAll)
+ row = engine->curMatch.indices[engine->curRow];
+
+ QModelIndex idx = createIndex(row, c->column);
+ if (!sourceIndex)
+ return idx;
+ return mapToSource(idx);
+}
+
+QModelIndex QCompletionModel::index(int row, int column, const QModelIndex& parent) const
+{
+ Q_D(const QCompletionModel);
+ if (row < 0 || column < 0 || column >= columnCount(parent) || parent.isValid())
+ return QModelIndex();
+
+ if (!showAll) {
+ if (!engine->matchCount())
+ return QModelIndex();
+ if (row >= engine->historyMatch.indices.count()) {
+ int want = row + 1 - engine->matchCount();
+ if (want > 0)
+ engine->filterOnDemand(want);
+ if (row >= engine->matchCount())
+ return QModelIndex();
+ }
+ } else {
+ if (row >= d->model->rowCount(engine->curParent))
+ return QModelIndex();
+ }
+
+ return createIndex(row, column);
+}
+
+int QCompletionModel::completionCount() const
+{
+ if (!engine->matchCount())
+ return 0;
+
+ engine->filterOnDemand(INT_MAX);
+ return engine->matchCount();
+}
+
+int QCompletionModel::rowCount(const QModelIndex &parent) const
+{
+ Q_D(const QCompletionModel);
+ if (parent.isValid())
+ return 0;
+
+ if (showAll) {
+ // Show all items below current parent, even if we have no valid matches
+ if (engine->curParts.count() != 1 && !engine->matchCount()
+ && !engine->curParent.isValid())
+ return 0;
+ return d->model->rowCount(engine->curParent);
+ }
+
+ return completionCount();
+}
+
+void QCompletionModel::setFiltered(bool filtered)
+{
+ if (showAll == !filtered)
+ return;
+ showAll = !filtered;
+ resetModel();
+}
+
+bool QCompletionModel::hasChildren(const QModelIndex &parent) const
+{
+ Q_D(const QCompletionModel);
+ if (parent.isValid())
+ return false;
+
+ if (showAll)
+ return d->model->hasChildren(mapToSource(parent));
+
+ if (!engine->matchCount())
+ return false;
+
+ return true;
+}
+
+QVariant QCompletionModel::data(const QModelIndex& index, int role) const
+{
+ Q_D(const QCompletionModel);
+ return d->model->data(mapToSource(index), role);
+}
+
+void QCompletionModel::modelDestroyed()
+{
+ QAbstractProxyModel::setSourceModel(0); // switch to static empty model
+ invalidate();
+}
+
+void QCompletionModel::rowsInserted()
+{
+ invalidate();
+ emit rowsAdded();
+}
+
+void QCompletionModel::invalidate()
+{
+ engine->cache.clear();
+ filter(engine->curParts);
+}
+
+void QCompletionModel::filter(const QStringList& parts)
+{
+ Q_D(QCompletionModel);
+ engine->filter(parts);
+ resetModel();
+
+ if (d->model->canFetchMore(engine->curParent))
+ d->model->fetchMore(engine->curParent);
+}
+
+void QCompletionModel::resetModel()
+{
+ if (rowCount() == 0) {
+ reset();
+ return;
+ }
+
+ emit layoutAboutToBeChanged();
+ QModelIndexList piList = persistentIndexList();
+ QModelIndexList empty;
+ for (int i = 0; i < piList.size(); i++)
+ empty.append(QModelIndex());
+ changePersistentIndexList(piList, empty);
+ emit layoutChanged();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+void QCompletionEngine::filter(const QStringList& parts)
+{
+ const QAbstractItemModel *model = c->proxy->sourceModel();
+ curParts = parts;
+ if (curParts.isEmpty())
+ curParts.append(QString());
+
+ curRow = -1;
+ curParent = QModelIndex();
+ curMatch = QMatchData();
+ historyMatch = filterHistory();
+
+ if (!model)
+ return;
+
+ QModelIndex parent;
+ for (int i = 0; i < curParts.count() - 1; i++) {
+ QString part = curParts[i];
+ int emi = filter(part, parent, -1).exactMatchIndex;
+ if (emi == -1)
+ return;
+ parent = model->index(emi, c->column, parent);
+ }
+
+ // Note that we set the curParent to a valid parent, even if we have no matches
+ // When filtering is disabled, we show all the items under this parent
+ curParent = parent;
+ if (curParts.last().isEmpty())
+ curMatch = QMatchData(QIndexMapper(0, model->rowCount(curParent) - 1), -1, false);
+ else
+ curMatch = filter(curParts.last(), curParent, 1); // build at least one
+ curRow = curMatch.isValid() ? 0 : -1;
+}
+
+QMatchData QCompletionEngine::filterHistory()
+{
+ QAbstractItemModel *source = c->proxy->sourceModel();
+ if (curParts.count() <= 1 || c->proxy->showAll || !source)
+ return QMatchData();
+ bool isDirModel = false;
+ bool isFsModel = false;
+#ifndef QT_NO_DIRMODEL
+ isDirModel = (qobject_cast<QDirModel *>(source) != 0);
+#endif
+#ifndef QT_NO_FILESYSTEMMODEL
+ isFsModel = (qobject_cast<QFileSystemModel *>(source) != 0);
+#endif
+ QVector<int> v;
+ QIndexMapper im(v);
+ QMatchData m(im, -1, true);
+
+ for (int i = 0; i < source->rowCount(); i++) {
+ QString str = source->index(i, c->column).data().toString();
+ if (str.startsWith(c->prefix, c->cs)
+#if (!defined(Q_OS_WIN) || defined(Q_OS_WINCE)) && !defined(Q_OS_SYMBIAN)
+ && ((!isFsModel && !isDirModel) || QDir::toNativeSeparators(str) != QDir::separator())
+#endif
+ )
+ m.indices.append(i);
+ }
+ return m;
+}
+
+// Returns a match hint from the cache by chopping the search string
+bool QCompletionEngine::matchHint(QString part, const QModelIndex& parent, QMatchData *hint)
+{
+ if (c->cs == Qt::CaseInsensitive)
+ part = part.toLower();
+
+ const CacheItem& map = cache[parent];
+
+ QString key = part;
+ while (!key.isEmpty()) {
+ key.chop(1);
+ if (map.contains(key)) {
+ *hint = map[key];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool QCompletionEngine::lookupCache(QString part, const QModelIndex& parent, QMatchData *m)
+{
+ if (c->cs == Qt::CaseInsensitive)
+ part = part.toLower();
+ const CacheItem& map = cache[parent];
+ if (!map.contains(part))
+ return false;
+ *m = map[part];
+ return true;
+}
+
+// When the cache size exceeds 1MB, it clears out about 1/2 of the cache.
+void QCompletionEngine::saveInCache(QString part, const QModelIndex& parent, const QMatchData& m)
+{
+ QMatchData old = cache[parent].take(part);
+ cost = cost + m.indices.cost() - old.indices.cost();
+ if (cost * sizeof(int) > 1024 * 1024) {
+ QMap<QModelIndex, CacheItem>::iterator it1 = cache.begin();
+ while (it1 != cache.end()) {
+ CacheItem& ci = it1.value();
+ int sz = ci.count()/2;
+ QMap<QString, QMatchData>::iterator it2 = ci.begin();
+ int i = 0;
+ while (it2 != ci.end() && i < sz) {
+ cost -= it2.value().indices.cost();
+ it2 = ci.erase(it2);
+ i++;
+ }
+ if (ci.count() == 0) {
+ it1 = cache.erase(it1);
+ } else {
+ ++it1;
+ }
+ }
+ }
+
+ if (c->cs == Qt::CaseInsensitive)
+ part = part.toLower();
+ cache[parent][part] = m;
+}
+
+///////////////////////////////////////////////////////////////////////////////////
+QIndexMapper QSortedModelEngine::indexHint(QString part, const QModelIndex& parent, Qt::SortOrder order)
+{
+ const QAbstractItemModel *model = c->proxy->sourceModel();
+
+ if (c->cs == Qt::CaseInsensitive)
+ part = part.toLower();
+
+ const CacheItem& map = cache[parent];
+
+ // Try to find a lower and upper bound for the search from previous results
+ int to = model->rowCount(parent) - 1;
+ int from = 0;
+ const CacheItem::const_iterator it = map.lowerBound(part);
+
+ // look backward for first valid hint
+ for(CacheItem::const_iterator it1 = it; it1-- != map.constBegin();) {
+ const QMatchData& value = it1.value();
+ if (value.isValid()) {
+ if (order == Qt::AscendingOrder) {
+ from = value.indices.last() + 1;
+ } else {
+ to = value.indices.first() - 1;
+ }
+ break;
+ }
+ }
+
+ // look forward for first valid hint
+ for(CacheItem::const_iterator it2 = it; it2 != map.constEnd(); ++it2) {
+ const QMatchData& value = it2.value();
+ if (value.isValid() && !it2.key().startsWith(part)) {
+ if (order == Qt::AscendingOrder) {
+ to = value.indices.first() - 1;
+ } else {
+ from = value.indices.first() + 1;
+ }
+ break;
+ }
+ }
+
+ return QIndexMapper(from, to);
+}
+
+Qt::SortOrder QSortedModelEngine::sortOrder(const QModelIndex &parent) const
+{
+ const QAbstractItemModel *model = c->proxy->sourceModel();
+
+ int rowCount = model->rowCount(parent);
+ if (rowCount < 2)
+ return Qt::AscendingOrder;
+ QString first = model->data(model->index(0, c->column, parent), c->role).toString();
+ QString last = model->data(model->index(rowCount - 1, c->column, parent), c->role).toString();
+ return QString::compare(first, last, c->cs) <= 0 ? Qt::AscendingOrder : Qt::DescendingOrder;
+}
+
+QMatchData QSortedModelEngine::filter(const QString& part, const QModelIndex& parent, int)
+{
+ const QAbstractItemModel *model = c->proxy->sourceModel();
+
+ QMatchData hint;
+ if (lookupCache(part, parent, &hint))
+ return hint;
+
+ QIndexMapper indices;
+ Qt::SortOrder order = sortOrder(parent);
+
+ if (matchHint(part, parent, &hint)) {
+ if (!hint.isValid())
+ return QMatchData();
+ indices = hint.indices;
+ } else {
+ indices = indexHint(part, parent, order);
+ }
+
+ // binary search the model within 'indices' for 'part' under 'parent'
+ int high = indices.to() + 1;
+ int low = indices.from() - 1;
+ int probe;
+ QModelIndex probeIndex;
+ QString probeData;
+
+ while (high - low > 1)
+ {
+ probe = (high + low) / 2;
+ probeIndex = model->index(probe, c->column, parent);
+ probeData = model->data(probeIndex, c->role).toString();
+ const int cmp = QString::compare(probeData, part, c->cs);
+ if ((order == Qt::AscendingOrder && cmp >= 0)
+ || (order == Qt::DescendingOrder && cmp < 0)) {
+ high = probe;
+ } else {
+ low = probe;
+ }
+ }
+
+ if ((order == Qt::AscendingOrder && low == indices.to())
+ || (order == Qt::DescendingOrder && high == indices.from())) { // not found
+ saveInCache(part, parent, QMatchData());
+ return QMatchData();
+ }
+
+ probeIndex = model->index(order == Qt::AscendingOrder ? low+1 : high-1, c->column, parent);
+ probeData = model->data(probeIndex, c->role).toString();
+ if (!probeData.startsWith(part, c->cs)) {
+ saveInCache(part, parent, QMatchData());
+ return QMatchData();
+ }
+
+ const bool exactMatch = QString::compare(probeData, part, c->cs) == 0;
+ int emi = exactMatch ? (order == Qt::AscendingOrder ? low+1 : high-1) : -1;
+
+ int from = 0;
+ int to = 0;
+ if (order == Qt::AscendingOrder) {
+ from = low + 1;
+ high = indices.to() + 1;
+ low = from;
+ } else {
+ to = high - 1;
+ low = indices.from() - 1;
+ high = to;
+ }
+
+ while (high - low > 1)
+ {
+ probe = (high + low) / 2;
+ probeIndex = model->index(probe, c->column, parent);
+ probeData = model->data(probeIndex, c->role).toString();
+ const bool startsWith = probeData.startsWith(part, c->cs);
+ if ((order == Qt::AscendingOrder && startsWith)
+ || (order == Qt::DescendingOrder && !startsWith)) {
+ low = probe;
+ } else {
+ high = probe;
+ }
+ }
+
+ QMatchData m(order == Qt::AscendingOrder ? QIndexMapper(from, high - 1) : QIndexMapper(low+1, to), emi, false);
+ saveInCache(part, parent, m);
+ return m;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+int QUnsortedModelEngine::buildIndices(const QString& str, const QModelIndex& parent, int n,
+ const QIndexMapper& indices, QMatchData* m)
+{
+ Q_ASSERT(m->partial);
+ Q_ASSERT(n != -1 || m->exactMatchIndex == -1);
+ const QAbstractItemModel *model = c->proxy->sourceModel();
+ int i, count = 0;
+
+ for (i = 0; i < indices.count() && count != n; ++i) {
+ QModelIndex idx = model->index(indices[i], c->column, parent);
+ QString data = model->data(idx, c->role).toString();
+ if (!data.startsWith(str, c->cs) || !(model->flags(idx) & Qt::ItemIsSelectable))
+ continue;
+ m->indices.append(indices[i]);
+ ++count;
+ if (m->exactMatchIndex == -1 && QString::compare(data, str, c->cs) == 0) {
+ m->exactMatchIndex = indices[i];
+ if (n == -1)
+ return indices[i];
+ }
+ }
+ return indices[i-1];
+}
+
+void QUnsortedModelEngine::filterOnDemand(int n)
+{
+ Q_ASSERT(matchCount());
+ if (!curMatch.partial)
+ return;
+ Q_ASSERT(n >= -1);
+ const QAbstractItemModel *model = c->proxy->sourceModel();
+ int lastRow = model->rowCount(curParent) - 1;
+ QIndexMapper im(curMatch.indices.last() + 1, lastRow);
+ int lastIndex = buildIndices(curParts.last(), curParent, n, im, &curMatch);
+ curMatch.partial = (lastRow != lastIndex);
+ saveInCache(curParts.last(), curParent, curMatch);
+}
+
+QMatchData QUnsortedModelEngine::filter(const QString& part, const QModelIndex& parent, int n)
+{
+ QMatchData hint;
+
+ QVector<int> v;
+ QIndexMapper im(v);
+ QMatchData m(im, -1, true);
+
+ const QAbstractItemModel *model = c->proxy->sourceModel();
+ bool foundInCache = lookupCache(part, parent, &m);
+
+ if (!foundInCache) {
+ if (matchHint(part, parent, &hint) && !hint.isValid())
+ return QMatchData();
+ }
+
+ if (!foundInCache && !hint.isValid()) {
+ const int lastRow = model->rowCount(parent) - 1;
+ QIndexMapper all(0, lastRow);
+ int lastIndex = buildIndices(part, parent, n, all, &m);
+ m.partial = (lastIndex != lastRow);
+ } else {
+ if (!foundInCache) { // build from hint as much as we can
+ buildIndices(part, parent, INT_MAX, hint.indices, &m);
+ m.partial = hint.partial;
+ }
+ if (m.partial && ((n == -1 && m.exactMatchIndex == -1) || (m.indices.count() < n))) {
+ // need more and have more
+ const int lastRow = model->rowCount(parent) - 1;
+ QIndexMapper rest(hint.indices.last() + 1, lastRow);
+ int want = n == -1 ? -1 : n - m.indices.count();
+ int lastIndex = buildIndices(part, parent, want, rest, &m);
+ m.partial = (lastRow != lastIndex);
+ }
+ }
+
+ saveInCache(part, parent, m);
+ return m;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+QCompleterPrivate::QCompleterPrivate()
+: widget(0), proxy(0), popup(0), cs(Qt::CaseSensitive), role(Qt::EditRole), column(0),
+ maxVisibleItems(7), sorting(QCompleter::UnsortedModel), wrap(true), eatFocusOut(true),
+ hiddenBecauseNoMatch(false)
+{
+}
+
+void QCompleterPrivate::init(QAbstractItemModel *m)
+{
+ Q_Q(QCompleter);
+ proxy = new QCompletionModel(this, q);
+ QObject::connect(proxy, SIGNAL(rowsAdded()), q, SLOT(_q_autoResizePopup()));
+ q->setModel(m);
+#ifdef QT_NO_LISTVIEW
+ q->setCompletionMode(QCompleter::InlineCompletion);
+#else
+ q->setCompletionMode(QCompleter::PopupCompletion);
+#endif // QT_NO_LISTVIEW
+}
+
+void QCompleterPrivate::setCurrentIndex(QModelIndex index, bool select)
+{
+ Q_Q(QCompleter);
+ if (!q->popup())
+ return;
+ if (!select) {
+ popup->selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
+ } else {
+ if (!index.isValid())
+ popup->selectionModel()->clear();
+ else
+ popup->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select
+ | QItemSelectionModel::Rows);
+ }
+ index = popup->selectionModel()->currentIndex();
+ if (!index.isValid())
+ popup->scrollToTop();
+ else
+ popup->scrollTo(index, QAbstractItemView::PositionAtTop);
+}
+
+void QCompleterPrivate::_q_completionSelected(const QItemSelection& selection)
+{
+ QModelIndex index;
+ if (!selection.indexes().isEmpty())
+ index = selection.indexes().first();
+
+ _q_complete(index, true);
+}
+
+void QCompleterPrivate::_q_complete(QModelIndex index, bool highlighted)
+{
+ Q_Q(QCompleter);
+ QString completion;
+
+ if (!index.isValid() || (!proxy->showAll && (index.row() >= proxy->engine->matchCount()))) {
+ completion = prefix;
+ } else {
+ if (!(index.flags() & Qt::ItemIsEnabled))
+ return;
+ QModelIndex si = proxy->mapToSource(index);
+ si = si.sibling(si.row(), column); // for clicked()
+ completion = q->pathFromIndex(si);
+#ifndef QT_NO_DIRMODEL
+ // add a trailing separator in inline
+ if (mode == QCompleter::InlineCompletion) {
+ if (qobject_cast<QDirModel *>(proxy->sourceModel()) && QFileInfo(completion).isDir())
+ completion += QDir::separator();
+ }
+#endif
+#ifndef QT_NO_FILESYSTEMMODEL
+ // add a trailing separator in inline
+ if (mode == QCompleter::InlineCompletion) {
+ if (qobject_cast<QFileSystemModel *>(proxy->sourceModel()) && QFileInfo(completion).isDir())
+ completion += QDir::separator();
+ }
+#endif
+ }
+
+ if (highlighted) {
+ emit q->highlighted(index);
+ emit q->highlighted(completion);
+ } else {
+ emit q->activated(index);
+ emit q->activated(completion);
+ }
+}
+
+void QCompleterPrivate::_q_autoResizePopup()
+{
+ if (!popup || !popup->isVisible())
+ return;
+ showPopup(popupRect);
+}
+
+void QCompleterPrivate::showPopup(const QRect& rect)
+{
+ const QRect screen = QApplication::desktop()->availableGeometry(widget);
+ Qt::LayoutDirection dir = widget->layoutDirection();
+ QPoint pos;
+ int rh, w;
+ int h = (popup->sizeHintForRow(0) * qMin(maxVisibleItems, popup->model()->rowCount()) + 3) + 3;
+ QScrollBar *hsb = popup->horizontalScrollBar();
+ if (hsb && hsb->isVisible())
+ h += popup->horizontalScrollBar()->sizeHint().height();
+
+ if (rect.isValid()) {
+ rh = rect.height();
+ w = rect.width();
+ pos = widget->mapToGlobal(dir == Qt::RightToLeft ? rect.bottomRight() : rect.bottomLeft());
+ } else {
+ rh = widget->height();
+ pos = widget->mapToGlobal(QPoint(0, widget->height() - 2));
+ w = widget->width();
+ }
+
+ if (w > screen.width())
+ w = screen.width();
+ if ((pos.x() + w) > (screen.x() + screen.width()))
+ pos.setX(screen.x() + screen.width() - w);
+ if (pos.x() < screen.x())
+ pos.setX(screen.x());
+
+ int top = pos.y() - rh - screen.top() + 2;
+ int bottom = screen.bottom() - pos.y();
+ h = qMax(h, popup->minimumHeight());
+ if (h > bottom) {
+ h = qMin(qMax(top, bottom), h);
+
+ if (top > bottom)
+ pos.setY(pos.y() - h - rh + 2);
+ }
+
+ popup->setGeometry(pos.x(), pos.y(), w, h);
+
+ if (!popup->isVisible())
+ popup->show();
+}
+
+void QCompleterPrivate::_q_fileSystemModelDirectoryLoaded(const QString &path)
+{
+ Q_Q(QCompleter);
+ // Slot called when QFileSystemModel has finished loading.
+ // If we hide the popup because there was no match because the model was not loaded yet,
+ // we re-start the completion when we get the results
+ if (hiddenBecauseNoMatch
+ && prefix.startsWith(path) && prefix != (path + QLatin1Char('/'))
+ && widget) {
+ q->complete();
+ }
+}
+
+/*!
+ Constructs a completer object with the given \a parent.
+*/
+QCompleter::QCompleter(QObject *parent)
+: QObject(*new QCompleterPrivate(), parent)
+{
+ Q_D(QCompleter);
+ d->init();
+}
+
+/*!
+ Constructs a completer object with the given \a parent that provides completions
+ from the specified \a model.
+*/
+QCompleter::QCompleter(QAbstractItemModel *model, QObject *parent)
+ : QObject(*new QCompleterPrivate(), parent)
+{
+ Q_D(QCompleter);
+ d->init(model);
+}
+
+#ifndef QT_NO_STRINGLISTMODEL
+/*!
+ Constructs a QCompleter object with the given \a parent that uses the specified
+ \a list as a source of possible completions.
+*/
+QCompleter::QCompleter(const QStringList& list, QObject *parent)
+: QObject(*new QCompleterPrivate(), parent)
+{
+ Q_D(QCompleter);
+ d->init(new QStringListModel(list, this));
+}
+#endif // QT_NO_STRINGLISTMODEL
+
+/*!
+ Destroys the completer object.
+*/
+QCompleter::~QCompleter()
+{
+}
+
+/*!
+ Sets the widget for which completion are provided for to \a widget. This
+ function is automatically called when a QCompleter is set on a QLineEdit
+ using QLineEdit::setCompleter() or on a QComboBox using
+ QComboBox::setCompleter(). The widget needs to be set explicitly when
+ providing completions for custom widgets.
+
+ \sa widget(), setModel(), setPopup()
+ */
+void QCompleter::setWidget(QWidget *widget)
+{
+ Q_D(QCompleter);
+ if (d->widget)
+ d->widget->removeEventFilter(this);
+ d->widget = widget;
+ if (d->widget)
+ d->widget->installEventFilter(this);
+ if (d->popup) {
+ d->popup->hide();
+ d->popup->setFocusProxy(d->widget);
+ }
+}
+
+/*!
+ Returns the widget for which the completer object is providing completions.
+
+ \sa setWidget()
+ */
+QWidget *QCompleter::widget() const
+{
+ Q_D(const QCompleter);
+ return d->widget;
+}
+
+/*!
+ Sets the model which provides completions to \a model. The \a model can
+ be list model or a tree model. If a model has been already previously set
+ and it has the QCompleter as its parent, it is deleted.
+
+ For convenience, if \a model is a QFileSystemModel, QCompleter switches its
+ caseSensitivity to Qt::CaseInsensitive on Windows and Qt::CaseSensitive
+ on other platforms.
+
+ \sa completionModel(), modelSorting, {Handling Tree Models}
+*/
+void QCompleter::setModel(QAbstractItemModel *model)
+{
+ Q_D(QCompleter);
+ QAbstractItemModel *oldModel = d->proxy->sourceModel();
+ d->proxy->setSourceModel(model);
+ if (d->popup)
+ setPopup(d->popup); // set the model and make new connections
+ if (oldModel && oldModel->QObject::parent() == this)
+ delete oldModel;
+#ifndef QT_NO_DIRMODEL
+ if (qobject_cast<QDirModel *>(model)) {
+#if (defined(Q_OS_WIN) && !defined(Q_OS_WINCE)) || defined(Q_OS_SYMBIAN)
+ setCaseSensitivity(Qt::CaseInsensitive);
+#else
+ setCaseSensitivity(Qt::CaseSensitive);
+#endif
+ }
+#endif // QT_NO_DIRMODEL
+#ifndef QT_NO_FILESYSTEMMODEL
+ QFileSystemModel *fsModel = qobject_cast<QFileSystemModel *>(model);
+ if (fsModel) {
+#if (defined(Q_OS_WIN) && !defined(Q_OS_WINCE)) || defined(Q_OS_SYMBIAN)
+ setCaseSensitivity(Qt::CaseInsensitive);
+#else
+ setCaseSensitivity(Qt::CaseSensitive);
+#endif
+ setCompletionRole(QFileSystemModel::FileNameRole);
+ connect(fsModel, SIGNAL(directoryLoaded(QString)), this, SLOT(_q_fileSystemModelDirectoryLoaded(QString)));
+ }
+#endif // QT_NO_FILESYSTEMMODEL
+}
+
+/*!
+ Returns the model that provides completion strings.
+
+ \sa completionModel()
+*/
+QAbstractItemModel *QCompleter::model() const
+{
+ Q_D(const QCompleter);
+ return d->proxy->sourceModel();
+}
+
+/*!
+ \enum QCompleter::CompletionMode
+
+ This enum specifies how completions are provided to the user.
+
+ \value PopupCompletion Current completions are displayed in a popup window.
+ \value InlineCompletion Completions appear inline (as selected text).
+ \value UnfilteredPopupCompletion All possible completions are displayed in a popup window with the most likely suggestion indicated as current.
+
+ \sa setCompletionMode()
+*/
+
+/*!
+ \property QCompleter::completionMode
+ \brief how the completions are provided to the user
+
+ The default value is QCompleter::PopupCompletion.
+*/
+void QCompleter::setCompletionMode(QCompleter::CompletionMode mode)
+{
+ Q_D(QCompleter);
+ d->mode = mode;
+ d->proxy->setFiltered(mode != QCompleter::UnfilteredPopupCompletion);
+
+ if (mode == QCompleter::InlineCompletion) {
+ if (d->widget)
+ d->widget->removeEventFilter(this);
+ if (d->popup) {
+ d->popup->deleteLater();
+ d->popup = 0;
+ }
+ } else {
+ if (d->widget)
+ d->widget->installEventFilter(this);
+ }
+}
+
+QCompleter::CompletionMode QCompleter::completionMode() const
+{
+ Q_D(const QCompleter);
+ return d->mode;
+}
+
+/*!
+ Sets the popup used to display completions to \a popup. QCompleter takes
+ ownership of the view.
+
+ A QListView is automatically created when the completionMode() is set to
+ QCompleter::PopupCompletion or QCompleter::UnfilteredPopupCompletion. The
+ default popup displays the completionColumn().
+
+ Ensure that this function is called before the view settings are modified.
+ This is required since view's properties may require that a model has been
+ set on the view (for example, hiding columns in the view requires a model
+ to be set on the view).
+
+ \sa popup()
+*/
+void QCompleter::setPopup(QAbstractItemView *popup)
+{
+ Q_D(QCompleter);
+ Q_ASSERT(popup != 0);
+ if (d->popup) {
+ QObject::disconnect(d->popup->selectionModel(), 0, this, 0);
+ QObject::disconnect(d->popup, 0, this, 0);
+ }
+ if (d->popup != popup)
+ delete d->popup;
+ if (popup->model() != d->proxy)
+ popup->setModel(d->proxy);
+#if defined(Q_OS_MAC) && !defined(QT_MAC_USE_COCOA)
+ popup->show();
+#else
+ popup->hide();
+#endif
+
+ Qt::FocusPolicy origPolicy = Qt::NoFocus;
+ if (d->widget)
+ origPolicy = d->widget->focusPolicy();
+ popup->setParent(0, Qt::Popup);
+ popup->setFocusPolicy(Qt::NoFocus);
+ if (d->widget)
+ d->widget->setFocusPolicy(origPolicy);
+
+ popup->setFocusProxy(d->widget);
+ popup->installEventFilter(this);
+ popup->setItemDelegate(new QCompleterItemDelegate(popup));
+#ifndef QT_NO_LISTVIEW
+ if (QListView *listView = qobject_cast<QListView *>(popup)) {
+ listView->setModelColumn(d->column);
+ }
+#endif
+
+ QObject::connect(popup, SIGNAL(clicked(QModelIndex)),
+ this, SLOT(_q_complete(QModelIndex)));
+ QObject::connect(this, SIGNAL(activated(QModelIndex)),
+ popup, SLOT(hide()));
+
+ QObject::connect(popup->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
+ this, SLOT(_q_completionSelected(QItemSelection)));
+ d->popup = popup;
+}
+
+/*!
+ Returns the popup used to display completions.
+
+ \sa setPopup()
+*/
+QAbstractItemView *QCompleter::popup() const
+{
+ Q_D(const QCompleter);
+#ifndef QT_NO_LISTVIEW
+ if (!d->popup && completionMode() != QCompleter::InlineCompletion) {
+ QListView *listView = new QListView;
+ listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ listView->setSelectionBehavior(QAbstractItemView::SelectRows);
+ listView->setSelectionMode(QAbstractItemView::SingleSelection);
+ listView->setModelColumn(d->column);
+ QCompleter *that = const_cast<QCompleter*>(this);
+ that->setPopup(listView);
+ }
+#endif // QT_NO_LISTVIEW
+ return d->popup;
+}
+
+/*!
+ \reimp
+*/
+bool QCompleter::event(QEvent *ev)
+{
+ return QObject::event(ev);
+}
+
+/*!
+ \reimp
+*/
+bool QCompleter::eventFilter(QObject *o, QEvent *e)
+{
+ Q_D(QCompleter);
+
+ if (d->eatFocusOut && o == d->widget && e->type() == QEvent::FocusOut) {
+ d->hiddenBecauseNoMatch = false;
+ if (d->popup && d->popup->isVisible())
+ return true;
+ }
+
+ if (o != d->popup)
+ return QObject::eventFilter(o, e);
+
+ switch (e->type()) {
+ case QEvent::KeyPress: {
+ QKeyEvent *ke = static_cast<QKeyEvent *>(e);
+
+ QModelIndex curIndex = d->popup->currentIndex();
+ QModelIndexList selList = d->popup->selectionModel()->selectedIndexes();
+
+ const int key = ke->key();
+ // In UnFilteredPopup mode, select the current item
+ if ((key == Qt::Key_Up || key == Qt::Key_Down) && selList.isEmpty() && curIndex.isValid()
+ && d->mode == QCompleter::UnfilteredPopupCompletion) {
+ d->setCurrentIndex(curIndex);
+ return true;
+ }
+
+ // Handle popup navigation keys. These are hardcoded because up/down might make the
+ // widget do something else (lineedit cursor moves to home/end on mac, for instance)
+ switch (key) {
+ case Qt::Key_End:
+ case Qt::Key_Home:
+ if (ke->modifiers() & Qt::ControlModifier)
+ return false;
+ break;
+
+ case Qt::Key_Up:
+ if (!curIndex.isValid()) {
+ int rowCount = d->proxy->rowCount();
+ QModelIndex lastIndex = d->proxy->index(rowCount - 1, d->column);
+ d->setCurrentIndex(lastIndex);
+ return true;
+ } else if (curIndex.row() == 0) {
+ if (d->wrap)
+ d->setCurrentIndex(QModelIndex());
+ return true;
+ }
+ return false;
+
+ case Qt::Key_Down:
+ if (!curIndex.isValid()) {
+ QModelIndex firstIndex = d->proxy->index(0, d->column);
+ d->setCurrentIndex(firstIndex);
+ return true;
+ } else if (curIndex.row() == d->proxy->rowCount() - 1) {
+ if (d->wrap)
+ d->setCurrentIndex(QModelIndex());
+ return true;
+ }
+ return false;
+
+ case Qt::Key_PageUp:
+ case Qt::Key_PageDown:
+ return false;
+ }
+
+ // Send the event to the widget. If the widget accepted the event, do nothing
+ // If the widget did not accept the event, provide a default implementation
+ d->eatFocusOut = false;
+ (static_cast<QObject *>(d->widget))->event(ke);
+ d->eatFocusOut = true;
+ if (!d->widget || e->isAccepted() || !d->popup->isVisible()) {
+ // widget lost focus, hide the popup
+ if (d->widget && (!d->widget->hasFocus()
+#ifdef QT_KEYPAD_NAVIGATION
+ || (QApplication::keypadNavigationEnabled() && !d->widget->hasEditFocus())
+#endif
+ ))
+ d->popup->hide();
+ if (e->isAccepted())
+ return true;
+ }
+
+ // default implementation for keys not handled by the widget when popup is open
+ switch (key) {
+#ifdef QT_KEYPAD_NAVIGATION
+ case Qt::Key_Select:
+ if (!QApplication::keypadNavigationEnabled())
+ break;
+#endif
+ case Qt::Key_Return:
+ case Qt::Key_Enter:
+ case Qt::Key_Tab:
+ d->popup->hide();
+ if (curIndex.isValid())
+ d->_q_complete(curIndex);
+ break;
+
+ case Qt::Key_F4:
+ if (ke->modifiers() & Qt::AltModifier)
+ d->popup->hide();
+ break;
+
+ case Qt::Key_Backtab:
+ case Qt::Key_Escape:
+ d->popup->hide();
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+ }
+
+#ifdef QT_KEYPAD_NAVIGATION
+ case QEvent::KeyRelease: {
+ QKeyEvent *ke = static_cast<QKeyEvent *>(e);
+ if (QApplication::keypadNavigationEnabled() && ke->key() == Qt::Key_Back) {
+ // Send the event to the 'widget'. This is what we did for KeyPress, so we need
+ // to do the same for KeyRelease, in case the widget's KeyPress event set
+ // up something (such as a timer) that is relying on also receiving the
+ // key release. I see this as a bug in Qt, and should really set it up for all
+ // the affected keys. However, it is difficult to tell how this will affect
+ // existing code, and I can't test for every combination!
+ d->eatFocusOut = false;
+ static_cast<QObject *>(d->widget)->event(ke);
+ d->eatFocusOut = true;
+ }
+ break;
+ }
+#endif
+
+ case QEvent::MouseButtonPress: {
+#ifdef QT_KEYPAD_NAVIGATION
+ if (QApplication::keypadNavigationEnabled()) {
+ // if we've clicked in the widget (or its descendant), let it handle the click
+ QWidget *source = qobject_cast<QWidget *>(o);
+ if (source) {
+ QPoint pos = source->mapToGlobal((static_cast<QMouseEvent *>(e))->pos());
+ QWidget *target = QApplication::widgetAt(pos);
+ if (target && (d->widget->isAncestorOf(target) ||
+ target == d->widget)) {
+ d->eatFocusOut = false;
+ static_cast<QObject *>(target)->event(e);
+ d->eatFocusOut = true;
+ return true;
+ }
+ }
+ }
+#endif
+ if (!d->popup->underMouse()) {
+ d->popup->hide();
+ return true;
+ }
+ }
+ return false;
+
+ case QEvent::InputMethod:
+ case QEvent::ShortcutOverride:
+ QApplication::sendEvent(d->widget, e);
+ break;
+
+ default:
+ return false;
+ }
+ return false;
+}
+
+/*!
+ For QCompleter::PopupCompletion and QCompletion::UnfilteredPopupCompletion
+ modes, calling this function displays the popup displaying the current
+ completions. By default, if \a rect is not specified, the popup is displayed
+ on the bottom of the widget(). If \a rect is specified the popup is
+ displayed on the left edge of the rectangle.
+
+ For QCompleter::InlineCompletion mode, the highlighted() signal is fired
+ with the current completion.
+*/
+void QCompleter::complete(const QRect& rect)
+{
+ Q_D(QCompleter);
+ QModelIndex idx = d->proxy->currentIndex(false);
+ d->hiddenBecauseNoMatch = false;
+ if (d->mode == QCompleter::InlineCompletion) {
+ if (idx.isValid())
+ d->_q_complete(idx, true);
+ return;
+ }
+
+ Q_ASSERT(d->widget != 0);
+ if ((d->mode == QCompleter::PopupCompletion && !idx.isValid())
+ || (d->mode == QCompleter::UnfilteredPopupCompletion && d->proxy->rowCount() == 0)) {
+ if (d->popup)
+ d->popup->hide(); // no suggestion, hide
+ d->hiddenBecauseNoMatch = true;
+ return;
+ }
+
+ popup();
+ if (d->mode == QCompleter::UnfilteredPopupCompletion)
+ d->setCurrentIndex(idx, false);
+
+ d->showPopup(rect);
+ d->popupRect = rect;
+}
+
+/*!
+ Sets the current row to the \a row specified. Returns true if successful;
+ otherwise returns false.
+
+ This function may be used along with currentCompletion() to iterate
+ through all the possible completions.
+
+ \sa currentCompletion(), completionCount()
+*/
+bool QCompleter::setCurrentRow(int row)
+{
+ Q_D(QCompleter);
+ return d->proxy->setCurrentRow(row);
+}
+
+/*!
+ Returns the current row.
+
+ \sa setCurrentRow()
+*/
+int QCompleter::currentRow() const
+{
+ Q_D(const QCompleter);
+ return d->proxy->currentRow();
+}
+
+/*!
+ Returns the number of completions for the current prefix. For an unsorted
+ model with a large number of items this can be expensive. Use setCurrentRow()
+ and currentCompletion() to iterate through all the completions.
+*/
+int QCompleter::completionCount() const
+{
+ Q_D(const QCompleter);
+ return d->proxy->completionCount();
+}
+
+/*!
+ \enum QCompleter::ModelSorting
+
+ This enum specifies how the items in the model are sorted.
+
+ \value UnsortedModel The model is unsorted.
+ \value CaseSensitivelySortedModel The model is sorted case sensitively.
+ \value CaseInsensitivelySortedModel The model is sorted case insensitively.
+
+ \sa setModelSorting()
+*/
+
+/*!
+ \property QCompleter::modelSorting
+ \brief the way the model is sorted
+
+ By default, no assumptions are made about the order of the items
+ in the model that provides the completions.
+
+ If the model's data for the completionColumn() and completionRole() is sorted in
+ ascending order, you can set this property to \l CaseSensitivelySortedModel
+ or \l CaseInsensitivelySortedModel. On large models, this can lead to
+ significant performance improvements because the completer object can
+ then use a binary search algorithm instead of linear search algorithm.
+
+ The sort order (i.e ascending or descending order) of the model is determined
+ dynamically by inspecting the contents of the model.
+
+ \bold{Note:} The performance improvements described above cannot take place
+ when the completer's \l caseSensitivity is different to the case sensitivity
+ used by the model's when sorting.
+
+ \sa setCaseSensitivity(), QCompleter::ModelSorting
+*/
+void QCompleter::setModelSorting(QCompleter::ModelSorting sorting)
+{
+ Q_D(QCompleter);
+ if (d->sorting == sorting)
+ return;
+ d->sorting = sorting;
+ d->proxy->createEngine();
+ d->proxy->invalidate();
+}
+
+QCompleter::ModelSorting QCompleter::modelSorting() const
+{
+ Q_D(const QCompleter);
+ return d->sorting;
+}
+
+/*!
+ \property QCompleter::completionColumn
+ \brief the column in the model in which completions are searched for.
+
+ If the popup() is a QListView, it is automatically setup to display
+ this column.
+
+ By default, the match column is 0.
+
+ \sa completionRole, caseSensitivity
+*/
+void QCompleter::setCompletionColumn(int column)
+{
+ Q_D(QCompleter);
+ if (d->column == column)
+ return;
+#ifndef QT_NO_LISTVIEW
+ if (QListView *listView = qobject_cast<QListView *>(d->popup))
+ listView->setModelColumn(column);
+#endif
+ d->column = column;
+ d->proxy->invalidate();
+}
+
+int QCompleter::completionColumn() const
+{
+ Q_D(const QCompleter);
+ return d->column;
+}
+
+/*!
+ \property QCompleter::completionRole
+ \brief the item role to be used to query the contents of items for matching.
+
+ The default role is Qt::EditRole.
+
+ \sa completionColumn, caseSensitivity
+*/
+void QCompleter::setCompletionRole(int role)
+{
+ Q_D(QCompleter);
+ if (d->role == role)
+ return;
+ d->role = role;
+ d->proxy->invalidate();
+}
+
+int QCompleter::completionRole() const
+{
+ Q_D(const QCompleter);
+ return d->role;
+}
+
+/*!
+ \property QCompleter::wrapAround
+ \brief the completions wrap around when navigating through items
+ \since 4.3
+
+ The default is true.
+*/
+void QCompleter::setWrapAround(bool wrap)
+{
+ Q_D(QCompleter);
+ if (d->wrap == wrap)
+ return;
+ d->wrap = wrap;
+}
+
+bool QCompleter::wrapAround() const
+{
+ Q_D(const QCompleter);
+ return d->wrap;
+}
+
+/*!
+ \property QCompleter::maxVisibleItems
+ \brief the maximum allowed size on screen of the completer, measured in items
+ \since 4.6
+
+ By default, this property has a value of 7.
+*/
+int QCompleter::maxVisibleItems() const
+{
+ Q_D(const QCompleter);
+ return d->maxVisibleItems;
+}
+
+void QCompleter::setMaxVisibleItems(int maxItems)
+{
+ Q_D(QCompleter);
+ if (maxItems < 0) {
+ qWarning("QCompleter::setMaxVisibleItems: "
+ "Invalid max visible items (%d) must be >= 0", maxItems);
+ return;
+ }
+ d->maxVisibleItems = maxItems;
+}
+
+/*!
+ \property QCompleter::caseSensitivity
+ \brief the case sensitivity of the matching
+
+ The default is Qt::CaseSensitive.
+
+ \sa completionColumn, completionRole, modelSorting
+*/
+void QCompleter::setCaseSensitivity(Qt::CaseSensitivity cs)
+{
+ Q_D(QCompleter);
+ if (d->cs == cs)
+ return;
+ d->cs = cs;
+ d->proxy->createEngine();
+ d->proxy->invalidate();
+}
+
+Qt::CaseSensitivity QCompleter::caseSensitivity() const
+{
+ Q_D(const QCompleter);
+ return d->cs;
+}
+
+/*!
+ \property QCompleter::completionPrefix
+ \brief the completion prefix used to provide completions.
+
+ The completionModel() is updated to reflect the list of possible
+ matches for \a prefix.
+*/
+void QCompleter::setCompletionPrefix(const QString &prefix)
+{
+ Q_D(QCompleter);
+ d->prefix = prefix;
+ d->proxy->filter(splitPath(prefix));
+}
+
+QString QCompleter::completionPrefix() const
+{
+ Q_D(const QCompleter);
+ return d->prefix;
+}
+
+/*!
+ Returns the model index of the current completion in the completionModel().
+
+ \sa setCurrentRow(), currentCompletion(), model()
+*/
+QModelIndex QCompleter::currentIndex() const
+{
+ Q_D(const QCompleter);
+ return d->proxy->currentIndex(false);
+}
+
+/*!
+ Returns the current completion string. This includes the \l completionPrefix.
+ When used alongside setCurrentRow(), it can be used to iterate through
+ all the matches.
+
+ \sa setCurrentRow(), currentIndex()
+*/
+QString QCompleter::currentCompletion() const
+{
+ Q_D(const QCompleter);
+ return pathFromIndex(d->proxy->currentIndex(true));
+}
+
+/*!
+ Returns the completion model. The completion model is a read-only list model
+ that contains all the possible matches for the current completion prefix.
+ The completion model is auto-updated to reflect the current completions.
+
+ \note The return value of this function is defined to be an QAbstractItemModel
+ purely for generality. This actual kind of model returned is an instance of an
+ QAbstractProxyModel subclass.
+
+ \sa completionPrefix, model()
+*/
+QAbstractItemModel *QCompleter::completionModel() const
+{
+ Q_D(const QCompleter);
+ return d->proxy;
+}
+
+/*!
+ Returns the path for the given \a index. The completer object uses this to
+ obtain the completion text from the underlying model.
+
+ The default implementation returns the \l{Qt::EditRole}{edit role} of the
+ item for list models. It returns the absolute file path if the model is a
+ QFileSystemModel.
+
+ \sa splitPath()
+*/
+
+QString QCompleter::pathFromIndex(const QModelIndex& index) const
+{
+ Q_D(const QCompleter);
+ if (!index.isValid())
+ return QString();
+
+ QAbstractItemModel *sourceModel = d->proxy->sourceModel();
+ if (!sourceModel)
+ return QString();
+ bool isDirModel = false;
+ bool isFsModel = false;
+#ifndef QT_NO_DIRMODEL
+ isDirModel = qobject_cast<QDirModel *>(d->proxy->sourceModel()) != 0;
+#endif
+#ifndef QT_NO_FILESYSTEMMODEL
+ isFsModel = qobject_cast<QFileSystemModel *>(d->proxy->sourceModel()) != 0;
+#endif
+ if (!isDirModel && !isFsModel)
+ return sourceModel->data(index, d->role).toString();
+
+ QModelIndex idx = index;
+ QStringList list;
+ do {
+ QString t;
+ if (isDirModel)
+ t = sourceModel->data(idx, Qt::EditRole).toString();
+#ifndef QT_NO_FILESYSTEMMODEL
+ else
+ t = sourceModel->data(idx, QFileSystemModel::FileNameRole).toString();
+#endif
+ list.prepend(t);
+ QModelIndex parent = idx.parent();
+ idx = parent.sibling(parent.row(), index.column());
+ } while (idx.isValid());
+
+#if (!defined(Q_OS_WIN) || defined(Q_OS_WINCE)) && !defined(Q_OS_SYMBIAN)
+ if (list.count() == 1) // only the separator or some other text
+ return list[0];
+ list[0].clear() ; // the join below will provide the separator
+#endif
+
+ return list.join(QDir::separator());
+}
+
+/*!
+ Splits the given \a path into strings that are used to match at each level
+ in the model().
+
+ The default implementation of splitPath() splits a file system path based on
+ QDir::separator() when the sourceModel() is a QFileSystemModel.
+
+ When used with list models, the first item in the returned list is used for
+ matching.
+
+ \sa pathFromIndex(), {Handling Tree Models}
+*/
+QStringList QCompleter::splitPath(const QString& path) const
+{
+ bool isDirModel = false;
+ bool isFsModel = false;
+#ifndef QT_NO_DIRMODEL
+ Q_D(const QCompleter);
+ isDirModel = qobject_cast<QDirModel *>(d->proxy->sourceModel()) != 0;
+#endif
+#ifndef QT_NO_FILESYSTEMMODEL
+#ifdef QT_NO_DIRMODEL
+ Q_D(const QCompleter);
+#endif
+ isFsModel = qobject_cast<QFileSystemModel *>(d->proxy->sourceModel()) != 0;
+#endif
+
+ if ((!isDirModel && !isFsModel) || path.isEmpty())
+ return QStringList(completionPrefix());
+
+ QString pathCopy = QDir::toNativeSeparators(path);
+ QString sep = QDir::separator();
+#if defined(Q_OS_SYMBIAN)
+ if (pathCopy == QLatin1String("\\"))
+ return QStringList(pathCopy);
+#elif defined(Q_OS_WIN) && !defined(Q_OS_WINCE)
+ if (pathCopy == QLatin1String("\\") || pathCopy == QLatin1String("\\\\"))
+ return QStringList(pathCopy);
+ QString doubleSlash(QLatin1String("\\\\"));
+ if (pathCopy.startsWith(doubleSlash))
+ pathCopy = pathCopy.mid(2);
+ else
+ doubleSlash.clear();
+#endif
+
+ QRegExp re(QLatin1Char('[') + QRegExp::escape(sep) + QLatin1Char(']'));
+ QStringList parts = pathCopy.split(re);
+
+#if defined(Q_OS_SYMBIAN)
+ // Do nothing
+#elif defined(Q_OS_WIN) && !defined(Q_OS_WINCE)
+ if (!doubleSlash.isEmpty())
+ parts[0].prepend(doubleSlash);
+#else
+ if (pathCopy[0] == sep[0]) // readd the "/" at the beginning as the split removed it
+ parts[0] = QDir::fromNativeSeparators(QString(sep[0]));
+#endif
+
+ return parts;
+}
+
+/*!
+ \fn void QCompleter::activated(const QModelIndex& index)
+
+ This signal is sent when an item in the popup() is activated by the user.
+ (by clicking or pressing return). The item's \a index in the completionModel()
+ is given.
+
+*/
+
+/*!
+ \fn void QCompleter::activated(const QString &text)
+
+ This signal is sent when an item in the popup() is activated by the user (by
+ clicking or pressing return). The item's \a text is given.
+
+*/
+
+/*!
+ \fn void QCompleter::highlighted(const QModelIndex& index)
+
+ This signal is sent when an item in the popup() is highlighted by
+ the user. It is also sent if complete() is called with the completionMode()
+ set to QCompleter::InlineCompletion. The item's \a index in the completionModel()
+ is given.
+*/
+
+/*!
+ \fn void QCompleter::highlighted(const QString &text)
+
+ This signal is sent when an item in the popup() is highlighted by
+ the user. It is also sent if complete() is called with the completionMode()
+ set to QCompleter::InlineCompletion. The item's \a text is given.
+*/
+
+QT_END_NAMESPACE
+
+#include "moc_qcompleter.cpp"
+
+#endif // QT_NO_COMPLETER
diff --git a/src/widgets/util/qcompleter.h b/src/widgets/util/qcompleter.h
new file mode 100644
index 0000000000..c7e1b6e945
--- /dev/null
+++ b/src/widgets/util/qcompleter.h
@@ -0,0 +1,171 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QCOMPLETER_H
+#define QCOMPLETER_H
+
+#include <QtCore/qobject.h>
+#include <QtCore/qpoint.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qabstractitemmodel.h>
+#include <QtCore/qrect.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Gui)
+
+#ifndef QT_NO_COMPLETER
+
+class QCompleterPrivate;
+class QAbstractItemView;
+class QAbstractProxyModel;
+class QWidget;
+
+class Q_WIDGETS_EXPORT QCompleter : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString completionPrefix READ completionPrefix WRITE setCompletionPrefix)
+ Q_PROPERTY(ModelSorting modelSorting READ modelSorting WRITE setModelSorting)
+ Q_PROPERTY(CompletionMode completionMode READ completionMode WRITE setCompletionMode)
+ Q_PROPERTY(int completionColumn READ completionColumn WRITE setCompletionColumn)
+ Q_PROPERTY(int completionRole READ completionRole WRITE setCompletionRole)
+ Q_PROPERTY(int maxVisibleItems READ maxVisibleItems WRITE setMaxVisibleItems)
+ Q_PROPERTY(Qt::CaseSensitivity caseSensitivity READ caseSensitivity WRITE setCaseSensitivity)
+ Q_PROPERTY(bool wrapAround READ wrapAround WRITE setWrapAround)
+
+public:
+ enum CompletionMode {
+ PopupCompletion,
+ UnfilteredPopupCompletion,
+ InlineCompletion
+ };
+
+ enum ModelSorting {
+ UnsortedModel = 0,
+ CaseSensitivelySortedModel,
+ CaseInsensitivelySortedModel
+ };
+
+ QCompleter(QObject *parent = 0);
+ QCompleter(QAbstractItemModel *model, QObject *parent = 0);
+#ifndef QT_NO_STRINGLISTMODEL
+ QCompleter(const QStringList& completions, QObject *parent = 0);
+#endif
+ ~QCompleter();
+
+ void setWidget(QWidget *widget);
+ QWidget *widget() const;
+
+ void setModel(QAbstractItemModel *c);
+ QAbstractItemModel *model() const;
+
+ void setCompletionMode(CompletionMode mode);
+ CompletionMode completionMode() const;
+
+ QAbstractItemView *popup() const;
+ void setPopup(QAbstractItemView *popup);
+
+ void setCaseSensitivity(Qt::CaseSensitivity caseSensitivity);
+ Qt::CaseSensitivity caseSensitivity() const;
+
+ void setModelSorting(ModelSorting sorting);
+ ModelSorting modelSorting() const;
+
+ void setCompletionColumn(int column);
+ int completionColumn() const;
+
+ void setCompletionRole(int role);
+ int completionRole() const;
+
+ bool wrapAround() const;
+
+ int maxVisibleItems() const;
+ void setMaxVisibleItems(int maxItems);
+
+ int completionCount() const;
+ bool setCurrentRow(int row);
+ int currentRow() const;
+
+ QModelIndex currentIndex() const;
+ QString currentCompletion() const;
+
+ QAbstractItemModel *completionModel() const;
+
+ QString completionPrefix() const;
+
+public Q_SLOTS:
+ void setCompletionPrefix(const QString &prefix);
+ void complete(const QRect& rect = QRect());
+ void setWrapAround(bool wrap);
+
+public:
+ virtual QString pathFromIndex(const QModelIndex &index) const;
+ virtual QStringList splitPath(const QString &path) const;
+
+protected:
+ bool eventFilter(QObject *o, QEvent *e);
+ bool event(QEvent *);
+
+Q_SIGNALS:
+ void activated(const QString &text);
+ void activated(const QModelIndex &index);
+ void highlighted(const QString &text);
+ void highlighted(const QModelIndex &index);
+
+private:
+ Q_DISABLE_COPY(QCompleter)
+ Q_DECLARE_PRIVATE(QCompleter)
+
+ Q_PRIVATE_SLOT(d_func(), void _q_complete(QModelIndex))
+ Q_PRIVATE_SLOT(d_func(), void _q_completionSelected(const QItemSelection&))
+ Q_PRIVATE_SLOT(d_func(), void _q_autoResizePopup())
+ Q_PRIVATE_SLOT(d_func(), void _q_fileSystemModelDirectoryLoaded(const QString&))
+};
+
+#endif // QT_NO_COMPLETER
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QCOMPLETER_H
diff --git a/src/widgets/util/qcompleter_p.h b/src/widgets/util/qcompleter_p.h
new file mode 100644
index 0000000000..639c875568
--- /dev/null
+++ b/src/widgets/util/qcompleter_p.h
@@ -0,0 +1,264 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QCOMPLETER_P_H
+#define QCOMPLETER_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"
+
+#ifndef QT_NO_COMPLETER
+
+#include "QtWidgets/qtreeview.h"
+#include "QtWidgets/qabstractproxymodel.h"
+#include "qcompleter.h"
+#include "QtWidgets/qitemdelegate.h"
+#include "QtGui/qpainter.h"
+#include "private/qabstractproxymodel_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QCompletionModel;
+
+class QCompleterPrivate : public QObjectPrivate
+{
+ Q_DECLARE_PUBLIC(QCompleter)
+
+public:
+ QCompleterPrivate();
+ ~QCompleterPrivate() { delete popup; }
+ void init(QAbstractItemModel *model = 0);
+
+ QPointer<QWidget> widget;
+ QCompletionModel *proxy;
+ QAbstractItemView *popup;
+ QCompleter::CompletionMode mode;
+
+ QString prefix;
+ Qt::CaseSensitivity cs;
+ int role;
+ int column;
+ int maxVisibleItems;
+ QCompleter::ModelSorting sorting;
+ bool wrap;
+
+ bool eatFocusOut;
+ QRect popupRect;
+ bool hiddenBecauseNoMatch;
+
+ void showPopup(const QRect&);
+ void _q_complete(QModelIndex, bool = false);
+ void _q_completionSelected(const QItemSelection&);
+ void _q_autoResizePopup();
+ void _q_fileSystemModelDirectoryLoaded(const QString &path);
+ void setCurrentIndex(QModelIndex, bool = true);
+};
+
+class QIndexMapper
+{
+public:
+ QIndexMapper() : v(false), f(0), t(-1) { }
+ QIndexMapper(int f, int t) : v(false), f(f), t(t) { }
+ QIndexMapper(QVector<int> vec) : v(true), vector(vec), f(-1), t(-1) { }
+
+ inline int count() const { return v ? vector.count() : t - f + 1; }
+ inline int operator[] (int index) const { return v ? vector[index] : f + index; }
+ inline int indexOf(int x) const { return v ? vector.indexOf(x) : ((t < f) ? -1 : x - f); }
+ inline bool isValid() const { return !isEmpty(); }
+ inline bool isEmpty() const { return v ? vector.isEmpty() : (t < f); }
+ inline void append(int x) { Q_ASSERT(v); vector.append(x); }
+ inline int first() const { return v ? vector.first() : f; }
+ inline int last() const { return v ? vector.last() : t; }
+ inline int from() const { Q_ASSERT(!v); return f; }
+ inline int to() const { Q_ASSERT(!v); return t; }
+ inline int cost() const { return vector.count()+2; }
+
+private:
+ bool v;
+ QVector<int> vector;
+ int f, t;
+};
+
+struct QMatchData {
+ QMatchData() : exactMatchIndex(-1) { }
+ QMatchData(const QIndexMapper& indices, int em, bool p) :
+ indices(indices), exactMatchIndex(em), partial(p) { }
+ QIndexMapper indices;
+ inline bool isValid() const { return indices.isValid(); }
+ int exactMatchIndex;
+ bool partial;
+};
+
+class QCompletionEngine
+{
+public:
+ typedef QMap<QString, QMatchData> CacheItem;
+ typedef QMap<QModelIndex, CacheItem> Cache;
+
+ QCompletionEngine(QCompleterPrivate *c) : c(c), curRow(-1), cost(0) { }
+ virtual ~QCompletionEngine() { }
+
+ void filter(const QStringList &parts);
+
+ QMatchData filterHistory();
+ bool matchHint(QString, const QModelIndex&, QMatchData*);
+
+ void saveInCache(QString, const QModelIndex&, const QMatchData&);
+ bool lookupCache(QString part, const QModelIndex& parent, QMatchData *m);
+
+ virtual void filterOnDemand(int) { }
+ virtual QMatchData filter(const QString&, const QModelIndex&, int) = 0;
+
+ int matchCount() const { return curMatch.indices.count() + historyMatch.indices.count(); }
+
+ QMatchData curMatch, historyMatch;
+ QCompleterPrivate *c;
+ QStringList curParts;
+ QModelIndex curParent;
+ int curRow;
+
+ Cache cache;
+ int cost;
+};
+
+class QSortedModelEngine : public QCompletionEngine
+{
+public:
+ QSortedModelEngine(QCompleterPrivate *c) : QCompletionEngine(c) { }
+ QMatchData filter(const QString&, const QModelIndex&, int);
+ QIndexMapper indexHint(QString, const QModelIndex&, Qt::SortOrder);
+ Qt::SortOrder sortOrder(const QModelIndex&) const;
+};
+
+class QUnsortedModelEngine : public QCompletionEngine
+{
+public:
+ QUnsortedModelEngine(QCompleterPrivate *c) : QCompletionEngine(c) { }
+
+ void filterOnDemand(int);
+ QMatchData filter(const QString&, const QModelIndex&, int);
+private:
+ int buildIndices(const QString& str, const QModelIndex& parent, int n,
+ const QIndexMapper& iv, QMatchData* m);
+};
+
+class QCompleterItemDelegate : public QItemDelegate
+{
+public:
+ QCompleterItemDelegate(QAbstractItemView *view)
+ : QItemDelegate(view), view(view) { }
+ void paint(QPainter *p, const QStyleOptionViewItem& opt, const QModelIndex& idx) const {
+ QStyleOptionViewItem optCopy = opt;
+ optCopy.showDecorationSelected = true;
+ if (view->currentIndex() == idx)
+ optCopy.state |= QStyle::State_HasFocus;
+ QItemDelegate::paint(p, optCopy, idx);
+ }
+
+private:
+ QAbstractItemView *view;
+};
+
+class QCompletionModelPrivate;
+
+class QCompletionModel : public QAbstractProxyModel
+{
+ Q_OBJECT
+
+public:
+ QCompletionModel(QCompleterPrivate *c, QObject *parent);
+
+ void createEngine();
+ void setFiltered(bool);
+ void filter(const QStringList& parts);
+ int completionCount() const;
+ int currentRow() const { return engine->curRow; }
+ bool setCurrentRow(int row);
+ QModelIndex currentIndex(bool) const;
+ void resetModel();
+
+ QModelIndex index(int row, int column, const QModelIndex & = QModelIndex()) const;
+ int rowCount(const QModelIndex &index = QModelIndex()) const;
+ int columnCount(const QModelIndex &index = QModelIndex()) const;
+ bool hasChildren(const QModelIndex &parent = QModelIndex()) const;
+ QModelIndex parent(const QModelIndex & = QModelIndex()) const { return QModelIndex(); }
+ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
+
+ void setSourceModel(QAbstractItemModel *sourceModel);
+ QModelIndex mapToSource(const QModelIndex& proxyIndex) const;
+ QModelIndex mapFromSource(const QModelIndex& sourceIndex) const;
+
+ QCompleterPrivate *c;
+ QScopedPointer<QCompletionEngine> engine;
+ bool showAll;
+
+ Q_DECLARE_PRIVATE(QCompletionModel)
+
+signals:
+ void rowsAdded();
+
+public Q_SLOTS:
+ void invalidate();
+ void rowsInserted();
+ void modelDestroyed();
+};
+
+class QCompletionModelPrivate : public QAbstractProxyModelPrivate
+{
+ Q_DECLARE_PUBLIC(QCompletionModel)
+};
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_COMPLETER
+
+#endif // QCOMPLETER_P_H
diff --git a/src/widgets/util/qflickgesture.cpp b/src/widgets/util/qflickgesture.cpp
new file mode 100644
index 0000000000..07eca6c53b
--- /dev/null
+++ b/src/widgets/util/qflickgesture.cpp
@@ -0,0 +1,715 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qgesture.h"
+#include "qapplication.h"
+#include "qevent.h"
+#include "qwidget.h"
+#include "qgraphicsitem.h"
+#include "qgraphicsscene.h"
+#include "qgraphicssceneevent.h"
+#include "qgraphicsview.h"
+#include "qscroller.h"
+#include "private/qevent_p.h"
+#include "private/qflickgesture_p.h"
+#include "qdebug.h"
+
+#ifndef QT_NO_GESTURES
+
+QT_BEGIN_NAMESPACE
+
+//#define QFLICKGESTURE_DEBUG
+
+#ifdef QFLICKGESTURE_DEBUG
+# define qFGDebug qDebug
+#else
+# define qFGDebug while (false) qDebug
+#endif
+
+extern bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event);
+
+static QMouseEvent *copyMouseEvent(QEvent *e)
+{
+ switch (e->type()) {
+ case QEvent::MouseButtonPress:
+ case QEvent::MouseButtonRelease:
+ case QEvent::MouseMove: {
+ QMouseEvent *me = static_cast<QMouseEvent *>(e);
+ return new QMouseEvent(me->type(), QPoint(0, 0), me->globalPos(), me->button(), me->buttons(), me->modifiers());
+ }
+#ifndef QT_NO_GRAPHICSVIEW
+ case QEvent::GraphicsSceneMousePress:
+ case QEvent::GraphicsSceneMouseRelease:
+ case QEvent::GraphicsSceneMouseMove: {
+ QGraphicsSceneMouseEvent *me = static_cast<QGraphicsSceneMouseEvent *>(e);
+#if 1
+ QEvent::Type met = me->type() == QEvent::GraphicsSceneMousePress ? QEvent::MouseButtonPress :
+ (me->type() == QEvent::GraphicsSceneMouseRelease ? QEvent::MouseButtonRelease : QEvent::MouseMove);
+ return new QMouseEvent(met, QPoint(0, 0), me->screenPos(), me->button(), me->buttons(), me->modifiers());
+#else
+ QGraphicsSceneMouseEvent *copy = new QGraphicsSceneMouseEvent(me->type());
+ copy->setPos(me->pos());
+ copy->setScenePos(me->scenePos());
+ copy->setScreenPos(me->screenPos());
+ for (int i = 0x1; i <= 0x10; i <<= 1) {
+ Qt::MouseButton button = Qt::MouseButton(i);
+ copy->setButtonDownPos(button, me->buttonDownPos(button));
+ copy->setButtonDownScenePos(button, me->buttonDownScenePos(button));
+ copy->setButtonDownScreenPos(button, me->buttonDownScreenPos(button));
+ }
+ copy->setLastPos(me->lastPos());
+ copy->setLastScenePos(me->lastScenePos());
+ copy->setLastScreenPos(me->lastScreenPos());
+ copy->setButtons(me->buttons());
+ copy->setButton(me->button());
+ copy->setModifiers(me->modifiers());
+ return copy;
+#endif
+ }
+#endif // QT_NO_GRAPHICSVIEW
+ default:
+ return 0;
+ }
+}
+
+class PressDelayHandler : public QObject
+{
+private:
+ PressDelayHandler(QObject *parent = 0)
+ : QObject(parent)
+ , pressDelayTimer(0)
+ , sendingEvent(false)
+ , mouseButton(Qt::NoButton)
+ , mouseTarget(0)
+ { }
+
+ static PressDelayHandler *inst;
+
+public:
+ enum {
+ UngrabMouseBefore = 1,
+ RegrabMouseAfterwards = 2
+ };
+
+ static PressDelayHandler *instance()
+ {
+ static PressDelayHandler *inst = 0;
+ if (!inst)
+ inst = new PressDelayHandler(QCoreApplication::instance());
+ return inst;
+ }
+
+ bool shouldEventBeIgnored(QEvent *) const
+ {
+ return sendingEvent;
+ }
+
+ bool isDelaying() const
+ {
+ return !pressDelayEvent.isNull();
+ }
+
+ void pressed(QEvent *e, int delay)
+ {
+ if (!pressDelayEvent) {
+ pressDelayEvent.reset(copyMouseEvent(e));
+ pressDelayTimer = startTimer(delay);
+ mouseTarget = QApplication::widgetAt(pressDelayEvent->globalPos());
+ mouseButton = pressDelayEvent->button();
+ qFGDebug() << "QFG: consuming/delaying mouse press";
+ } else {
+ qFGDebug() << "QFG: NOT consuming/delaying mouse press";
+ }
+ e->setAccepted(true);
+ }
+
+ bool released(QEvent *e, bool scrollerWasActive, bool scrollerIsActive)
+ {
+ // consume this event if the scroller was or is active
+ bool result = scrollerWasActive || scrollerIsActive;
+
+ // stop the timer
+ if (pressDelayTimer) {
+ killTimer(pressDelayTimer);
+ pressDelayTimer = 0;
+ }
+ // we still haven't even sent the press, so do it now
+ if (pressDelayEvent && mouseTarget && !scrollerIsActive) {
+ QScopedPointer<QMouseEvent> releaseEvent(copyMouseEvent(e));
+
+ qFGDebug() << "QFG: re-sending mouse press (due to release) for " << mouseTarget;
+ sendMouseEvent(pressDelayEvent.data(), UngrabMouseBefore);
+
+ qFGDebug() << "QFG: faking mouse release (due to release) for " << mouseTarget;
+ sendMouseEvent(releaseEvent.data());
+
+ result = true; // consume this event
+ } else if (mouseTarget && scrollerIsActive) {
+ // we grabbed the mouse expicitly when the scroller became active, so undo that now
+ sendMouseEvent(0, UngrabMouseBefore);
+ }
+ pressDelayEvent.reset(0);
+ mouseTarget = 0;
+ return result;
+ }
+
+ void scrollerWasIntercepted()
+ {
+ qFGDebug() << "QFG: deleting delayed mouse press, since scroller was only intercepted";
+ if (pressDelayEvent) {
+ // we still haven't even sent the press, so just throw it away now
+ if (pressDelayTimer) {
+ killTimer(pressDelayTimer);
+ pressDelayTimer = 0;
+ }
+ pressDelayEvent.reset(0);
+ }
+ mouseTarget = 0;
+ }
+
+ void scrollerBecameActive()
+ {
+ if (pressDelayEvent) {
+ // we still haven't even sent the press, so just throw it away now
+ qFGDebug() << "QFG: deleting delayed mouse press, since scroller is active now";
+ if (pressDelayTimer) {
+ killTimer(pressDelayTimer);
+ pressDelayTimer = 0;
+ }
+ pressDelayEvent.reset(0);
+ mouseTarget = 0;
+ } else if (mouseTarget) {
+ // we did send a press, so we need to fake a release now
+
+ // release all pressed mouse buttons
+ /* Qt::MouseButtons mouseButtons = QApplication::mouseButtons();
+ for (int i = 0; i < 32; ++i) {
+ if (mouseButtons & (1 << i)) {
+ Qt::MouseButton b = static_cast<Qt::MouseButton>(1 << i);
+ mouseButtons &= ~b;
+ QPoint farFarAway(-QWIDGETSIZE_MAX, -QWIDGETSIZE_MAX);
+
+ qFGDebug() << "QFG: sending a fake mouse release at far-far-away to " << mouseTarget;
+ QMouseEvent re(QEvent::MouseButtonRelease, QPoint(), farFarAway,
+ b, mouseButtons, QApplication::keyboardModifiers());
+ sendMouseEvent(&re);
+ }
+ }*/
+
+ QPoint farFarAway(-QWIDGETSIZE_MAX, -QWIDGETSIZE_MAX);
+
+ qFGDebug() << "QFG: sending a fake mouse release at far-far-away to " << mouseTarget;
+ QMouseEvent re(QEvent::MouseButtonRelease, QPoint(), farFarAway,
+ mouseButton, QApplication::mouseButtons() & ~mouseButton,
+ QApplication::keyboardModifiers());
+ sendMouseEvent(&re, RegrabMouseAfterwards);
+ // don't clear the mouseTarget just yet, since we need to explicitly ungrab the mouse on release!
+ }
+ }
+
+protected:
+ void timerEvent(QTimerEvent *e)
+ {
+ if (e->timerId() == pressDelayTimer) {
+ if (pressDelayEvent && mouseTarget) {
+ qFGDebug() << "QFG: timer event: re-sending mouse press to " << mouseTarget;
+ sendMouseEvent(pressDelayEvent.data(), UngrabMouseBefore);
+ }
+ pressDelayEvent.reset(0);
+
+ if (pressDelayTimer) {
+ killTimer(pressDelayTimer);
+ pressDelayTimer = 0;
+ }
+ }
+ }
+
+ void sendMouseEvent(QMouseEvent *me, int flags = 0)
+ {
+ if (mouseTarget) {
+ sendingEvent = true;
+
+#ifndef QT_NO_GRAPHICSVIEW
+ QGraphicsItem *grabber = 0;
+ if (mouseTarget->parentWidget()) {
+ if (QGraphicsView *gv = qobject_cast<QGraphicsView *>(mouseTarget->parentWidget())) {
+ if (gv->scene())
+ grabber = gv->scene()->mouseGrabberItem();
+ }
+ }
+
+ if (grabber && (flags & UngrabMouseBefore)) {
+ // GraphicsView Mouse Handling Workaround #1:
+ // we need to ungrab the mouse before re-sending the press,
+ // since the scene had already set the mouse grabber to the
+ // original (and consumed) event's receiver
+ qFGDebug() << "QFG: ungrabbing" << grabber;
+ grabber->ungrabMouse();
+ }
+#endif // QT_NO_GRAPHICSVIEW
+
+ if (me) {
+ QMouseEvent copy(me->type(), mouseTarget->mapFromGlobal(me->globalPos()), me->globalPos(), me->button(), me->buttons(), me->modifiers());
+ qt_sendSpontaneousEvent(mouseTarget, &copy);
+ }
+
+#ifndef QT_NO_GRAPHICSVIEW
+ if (grabber && (flags & RegrabMouseAfterwards)) {
+ // GraphicsView Mouse Handling Workaround #2:
+ // we need to re-grab the mouse after sending a faked mouse
+ // release, since we still need the mouse moves for the gesture
+ // (the scene will clear the item's mouse grabber status on
+ // release).
+ qFGDebug() << "QFG: re-grabbing" << grabber;
+ grabber->grabMouse();
+ }
+#endif
+ sendingEvent = false;
+ }
+ }
+
+
+private:
+ int pressDelayTimer;
+ QScopedPointer<QMouseEvent> pressDelayEvent;
+ bool sendingEvent;
+ Qt::MouseButton mouseButton;
+ QPointer<QWidget> mouseTarget;
+};
+
+
+/*!
+ \internal
+ \class QFlickGesture
+ \since 4.8
+ \brief The QFlickGesture class describes a flicking gesture made by the user.
+ \ingroup gestures
+ The QFlickGesture is more complex than the QPanGesture that uses QScroller and QScrollerProperties
+ to decide if it is triggered.
+ This gesture is reacting on touch event as compared to the QMouseFlickGesture.
+
+ \sa {Gestures Programming}, QScroller, QScrollerProperties, QMouseFlickGesture
+*/
+
+/*!
+ \internal
+*/
+QFlickGesture::QFlickGesture(QObject *receiver, Qt::MouseButton button, QObject *parent)
+ : QGesture(*new QFlickGesturePrivate, parent)
+{
+ d_func()->q_ptr = this;
+ d_func()->receiver = receiver;
+ d_func()->receiverScroller = (receiver && QScroller::hasScroller(receiver)) ? QScroller::scroller(receiver) : 0;
+ d_func()->button = button;
+}
+
+QFlickGesture::~QFlickGesture()
+{ }
+
+QFlickGesturePrivate::QFlickGesturePrivate()
+ : receiverScroller(0), button(Qt::NoButton), macIgnoreWheel(false)
+{ }
+
+
+//
+// QFlickGestureRecognizer
+//
+
+
+QFlickGestureRecognizer::QFlickGestureRecognizer(Qt::MouseButton button)
+{
+ this->button = button;
+}
+
+/*! \reimp
+ */
+QGesture *QFlickGestureRecognizer::create(QObject *target)
+{
+#ifndef QT_NO_GRAPHICSVIEW
+ QGraphicsObject *go = qobject_cast<QGraphicsObject*>(target);
+ if (go && button == Qt::NoButton) {
+ go->setAcceptTouchEvents(true);
+ }
+#endif
+ return new QFlickGesture(target, button);
+}
+
+/*! \internal
+ The recognize function detects a touch event suitable to start the attached QScroller.
+ The QFlickGesture will be triggered as soon as the scroller is no longer in the state
+ QScroller::Inactive or QScroller::Pressed. It will be finished or canceled
+ at the next QEvent::TouchEnd.
+ Note that the QScroller might continue scrolling (kinetically) at this point.
+ */
+QGestureRecognizer::Result QFlickGestureRecognizer::recognize(QGesture *state,
+ QObject *watched,
+ QEvent *event)
+{
+ Q_UNUSED(watched);
+
+ static QElapsedTimer monotonicTimer;
+ if (!monotonicTimer.isValid())
+ monotonicTimer.start();
+
+ QFlickGesture *q = static_cast<QFlickGesture *>(state);
+ QFlickGesturePrivate *d = q->d_func();
+
+ QScroller *scroller = d->receiverScroller;
+ if (!scroller)
+ return Ignore; // nothing to do without a scroller?
+
+ QWidget *receiverWidget = qobject_cast<QWidget *>(d->receiver);
+#ifndef QT_NO_GRAPHICSVIEW
+ QGraphicsObject *receiverGraphicsObject = qobject_cast<QGraphicsObject *>(d->receiver);
+#endif
+
+ // this is only set for events that we inject into the event loop via sendEvent()
+ if (PressDelayHandler::instance()->shouldEventBeIgnored(event)) {
+ //qFGDebug() << state << "QFG: ignored event: " << event->type();
+ return Ignore;
+ }
+
+ const QMouseEvent *me = 0;
+#ifndef QT_NO_GRAPHICSVIEW
+ const QGraphicsSceneMouseEvent *gsme = 0;
+#endif
+ const QTouchEvent *te = 0;
+ QPoint globalPos;
+
+ // qFGDebug() << "FlickGesture "<<state<<"watched:"<<watched<<"receiver"<<d->receiver<<"event"<<event->type()<<"button"<<button;
+
+ switch (event->type()) {
+ case QEvent::MouseButtonPress:
+ case QEvent::MouseButtonRelease:
+ case QEvent::MouseMove:
+ if (!receiverWidget)
+ return Ignore;
+ if (button != Qt::NoButton) {
+ me = static_cast<const QMouseEvent *>(event);
+ globalPos = me->globalPos();
+ }
+ break;
+#ifndef QT_NO_GRAPHICSVIEW
+ case QEvent::GraphicsSceneMousePress:
+ case QEvent::GraphicsSceneMouseRelease:
+ case QEvent::GraphicsSceneMouseMove:
+ if (!receiverGraphicsObject)
+ return Ignore;
+ if (button != Qt::NoButton) {
+ gsme = static_cast<const QGraphicsSceneMouseEvent *>(event);
+ globalPos = gsme->screenPos();
+ }
+ break;
+#endif
+ case QEvent::TouchBegin:
+ case QEvent::TouchEnd:
+ case QEvent::TouchUpdate:
+ if (button == Qt::NoButton) {
+ te = static_cast<const QTouchEvent *>(event);
+ if (!te->touchPoints().isEmpty())
+ globalPos = te->touchPoints().at(0).screenPos().toPoint();
+ }
+ break;
+
+#if defined(Q_WS_MAC)
+ // the only way to distinguish between real mouse wheels and wheel
+ // events generated by the native 2 finger swipe gesture is to listen
+ // for these events (according to Apple's Cocoa Event-Handling Guide)
+
+ case QEvent::NativeGesture: {
+ QNativeGestureEvent *nge = static_cast<QNativeGestureEvent *>(event);
+ if (nge->gestureType == QNativeGestureEvent::GestureBegin)
+ d->macIgnoreWheel = true;
+ else if (nge->gestureType == QNativeGestureEvent::GestureEnd)
+ d->macIgnoreWheel = false;
+ break;
+ }
+#endif
+
+ // consume all wheel events if the scroller is active
+ case QEvent::Wheel:
+ if (d->macIgnoreWheel || (scroller->state() != QScroller::Inactive))
+ return Ignore | ConsumeEventHint;
+ break;
+
+ // consume all dbl click events if the scroller is active
+ case QEvent::MouseButtonDblClick:
+ if (scroller->state() != QScroller::Inactive)
+ return Ignore | ConsumeEventHint;
+ break;
+
+ default:
+ break;
+ }
+
+ if (!me
+#ifndef QT_NO_GRAPHICSVIEW
+ && !gsme
+#endif
+ && !te) // Neither mouse nor touch
+ return Ignore;
+
+ // get the current pointer position in local coordinates.
+ QPointF point;
+ QScroller::Input inputType = (QScroller::Input) 0;
+
+ switch (event->type()) {
+ case QEvent::MouseButtonPress:
+ if (me && me->button() == button && me->buttons() == button) {
+ point = me->globalPos();
+ inputType = QScroller::InputPress;
+ } else if (me) {
+ scroller->stop();
+ return CancelGesture;
+ }
+ break;
+ case QEvent::MouseButtonRelease:
+ if (me && me->button() == button) {
+ point = me->globalPos();
+ inputType = QScroller::InputRelease;
+ }
+ break;
+ case QEvent::MouseMove:
+#ifdef Q_OS_SYMBIAN
+ // Qt on Symbian tracks the button state internally, while Qt on Win/Mac/Unix
+ // relies on the windowing system to report the current buttons state.
+ if (me && (me->buttons() == button || !me->buttons())) {
+#else
+ if (me && me->buttons() == button) {
+#endif
+ point = me->globalPos();
+ inputType = QScroller::InputMove;
+ }
+ break;
+
+#ifndef QT_NO_GRAPHICSVIEW
+ case QEvent::GraphicsSceneMousePress:
+ if (gsme && gsme->button() == button && gsme->buttons() == button) {
+ point = gsme->scenePos();
+ inputType = QScroller::InputPress;
+ } else if (gsme) {
+ scroller->stop();
+ return CancelGesture;
+ }
+ break;
+ case QEvent::GraphicsSceneMouseRelease:
+ if (gsme && gsme->button() == button) {
+ point = gsme->scenePos();
+ inputType = QScroller::InputRelease;
+ }
+ break;
+ case QEvent::GraphicsSceneMouseMove:
+#ifdef Q_OS_SYMBIAN
+ // Qt on Symbian tracks the button state internally, while Qt on Win/Mac/Unix
+ // relies on the windowing system to report the current buttons state.
+ if (gsme && (gsme->buttons() == button || !me->buttons())) {
+#else
+ if (gsme && gsme->buttons() == button) {
+#endif
+ point = gsme->scenePos();
+ inputType = QScroller::InputMove;
+ }
+ break;
+#endif
+
+ case QEvent::TouchBegin:
+ inputType = QScroller::InputPress;
+ // fall through
+ case QEvent::TouchEnd:
+ if (!inputType)
+ inputType = QScroller::InputRelease;
+ // fallthrough
+ case QEvent::TouchUpdate:
+ if (!inputType)
+ inputType = QScroller::InputMove;
+
+ if (te->deviceType() == QTouchEvent::TouchPad) {
+ if (te->touchPoints().count() != 2) // 2 fingers on pad
+ return Ignore;
+
+ point = te->touchPoints().at(0).startScenePos() +
+ ((te->touchPoints().at(0).scenePos() - te->touchPoints().at(0).startScenePos()) +
+ (te->touchPoints().at(1).scenePos() - te->touchPoints().at(1).startScenePos())) / 2;
+ } else { // TouchScreen
+ if (te->touchPoints().count() != 1) // 1 finger on screen
+ return Ignore;
+
+ point = te->touchPoints().at(0).scenePos();
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ // Check for an active scroller at globalPos
+ if (inputType == QScroller::InputPress) {
+ foreach (QScroller *as, QScroller::activeScrollers()) {
+ if (as != scroller) {
+ QRegion scrollerRegion;
+
+ if (QWidget *w = qobject_cast<QWidget *>(as->target())) {
+ scrollerRegion = QRect(w->mapToGlobal(QPoint(0, 0)), w->size());
+#ifndef QT_NO_GRAPHICSVIEW
+ } else if (QGraphicsObject *go = qobject_cast<QGraphicsObject *>(as->target())) {
+ if (go->scene() && !go->scene()->views().isEmpty()) {
+ foreach (QGraphicsView *gv, go->scene()->views())
+ scrollerRegion |= gv->mapFromScene(go->mapToScene(go->boundingRect()))
+ .translated(gv->mapToGlobal(QPoint(0, 0)));
+ }
+#endif
+ }
+ // active scrollers always have priority
+ if (scrollerRegion.contains(globalPos))
+ return Ignore;
+ }
+ }
+ }
+
+ bool scrollerWasDragging = (scroller->state() == QScroller::Dragging);
+ bool scrollerWasScrolling = (scroller->state() == QScroller::Scrolling);
+
+ if (inputType) {
+ if (QWidget *w = qobject_cast<QWidget *>(d->receiver))
+ point = w->mapFromGlobal(point.toPoint());
+#ifndef QT_NO_GRAPHICSVIEW
+ else if (QGraphicsObject *go = qobject_cast<QGraphicsObject *>(d->receiver))
+ point = go->mapFromScene(point);
+#endif
+
+ // inform the scroller about the new event
+ scroller->handleInput(inputType, point, monotonicTimer.elapsed());
+ }
+
+ // depending on the scroller state return the gesture state
+ Result result(0);
+ bool scrollerIsActive = (scroller->state() == QScroller::Dragging ||
+ scroller->state() == QScroller::Scrolling);
+
+ // Consume all mouse events while dragging or scrolling to avoid nasty
+ // side effects with Qt's standard widgets.
+ if ((me
+#ifndef QT_NO_GRAPHICSVIEW
+ || gsme
+#endif
+ ) && scrollerIsActive)
+ result |= ConsumeEventHint;
+
+ // The only problem with this approach is that we consume the
+ // MouseRelease when we start the scrolling with a flick gesture, so we
+ // have to fake a MouseRelease "somewhere" to not mess with the internal
+ // states of Qt's widgets (a QPushButton would stay in 'pressed' state
+ // forever, if it doesn't receive a MouseRelease).
+ if (me
+#ifndef QT_NO_GRAPHICSVIEW
+ || gsme
+#endif
+ ) {
+ if (!scrollerWasDragging && !scrollerWasScrolling && scrollerIsActive)
+ PressDelayHandler::instance()->scrollerBecameActive();
+ else if (scrollerWasScrolling && (scroller->state() == QScroller::Dragging || scroller->state() == QScroller::Inactive))
+ PressDelayHandler::instance()->scrollerWasIntercepted();
+ }
+
+ if (!inputType) {
+ result |= Ignore;
+ } else {
+ switch (event->type()) {
+ case QEvent::MouseButtonPress:
+#ifndef QT_NO_GRAPHICSVIEW
+ case QEvent::GraphicsSceneMousePress:
+#endif
+ if (scroller->state() == QScroller::Pressed) {
+ int pressDelay = int(1000 * scroller->scrollerProperties().scrollMetric(QScrollerProperties::MousePressEventDelay).toReal());
+ if (pressDelay > 0) {
+ result |= ConsumeEventHint;
+
+ PressDelayHandler::instance()->pressed(event, pressDelay);
+ event->accept();
+ }
+ }
+ // fall through
+ case QEvent::TouchBegin:
+ q->setHotSpot(globalPos);
+ result |= scrollerIsActive ? TriggerGesture : MayBeGesture;
+ break;
+
+ case QEvent::MouseMove:
+#ifndef QT_NO_GRAPHICSVIEW
+ case QEvent::GraphicsSceneMouseMove:
+#endif
+ if (PressDelayHandler::instance()->isDelaying())
+ result |= ConsumeEventHint;
+ // fall through
+ case QEvent::TouchUpdate:
+ result |= scrollerIsActive ? TriggerGesture : Ignore;
+ break;
+
+#ifndef QT_NO_GRAPHICSVIEW
+ case QEvent::GraphicsSceneMouseRelease:
+#endif
+ case QEvent::MouseButtonRelease:
+ if (PressDelayHandler::instance()->released(event, scrollerWasDragging || scrollerWasScrolling, scrollerIsActive))
+ result |= ConsumeEventHint;
+ // fall through
+ case QEvent::TouchEnd:
+ result |= scrollerIsActive ? FinishGesture : CancelGesture;
+ break;
+
+ default:
+ result |= Ignore;
+ break;
+ }
+ }
+ return result;
+}
+
+
+/*! \reimp
+ */
+void QFlickGestureRecognizer::reset(QGesture *state)
+{
+ QGestureRecognizer::reset(state);
+}
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_GESTURES
diff --git a/src/widgets/util/qflickgesture_p.h b/src/widgets/util/qflickgesture_p.h
new file mode 100644
index 0000000000..23c8b6e788
--- /dev/null
+++ b/src/widgets/util/qflickgesture_p.h
@@ -0,0 +1,113 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QFLICKGESTURE_P_H
+#define QFLICKGESTURE_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 "qevent.h"
+#include "qgesturerecognizer.h"
+#include "private/qgesture_p.h"
+#include "qscroller.h"
+#include "qscopedpointer.h"
+
+#ifndef QT_NO_GESTURES
+
+QT_BEGIN_NAMESPACE
+
+class QFlickGesturePrivate;
+class QGraphicsItem;
+
+class Q_WIDGETS_EXPORT QFlickGesture : public QGesture
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(QFlickGesture)
+
+public:
+ QFlickGesture(QObject *receiver, Qt::MouseButton button, QObject *parent = 0);
+ ~QFlickGesture();
+
+ friend class QFlickGestureRecognizer;
+};
+
+class PressDelayHandler;
+
+class QFlickGesturePrivate : public QGesturePrivate
+{
+ Q_DECLARE_PUBLIC(QFlickGesture)
+public:
+ QFlickGesturePrivate();
+
+ QPointer<QObject> receiver;
+ QScroller *receiverScroller;
+ Qt::MouseButton button; // NoButton == Touch
+ bool macIgnoreWheel;
+ static PressDelayHandler *pressDelayHandler;
+};
+
+class QFlickGestureRecognizer : public QGestureRecognizer
+{
+public:
+ QFlickGestureRecognizer(Qt::MouseButton button);
+
+ QGesture *create(QObject *target);
+ QGestureRecognizer::Result recognize(QGesture *state, QObject *watched, QEvent *event);
+ void reset(QGesture *state);
+
+private:
+ Qt::MouseButton button; // NoButton == Touch
+};
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_GESTURES
+
+#endif // QFLICKGESTURE_P_H
diff --git a/src/widgets/util/qscroller.cpp b/src/widgets/util/qscroller.cpp
new file mode 100644
index 0000000000..7ad726228d
--- /dev/null
+++ b/src/widgets/util/qscroller.cpp
@@ -0,0 +1,2056 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qevent.h"
+#include "qwidget.h"
+#include "qscroller.h"
+#include "private/qflickgesture_p.h"
+#include "private/qscroller_p.h"
+#include "qscrollerproperties.h"
+#include "private/qscrollerproperties_p.h"
+#include "qnumeric.h"
+#include "math.h"
+
+#include <QTime>
+#include <QElapsedTimer>
+#include <QMap>
+#include <QApplication>
+#include <QAbstractScrollArea>
+#include <QGraphicsObject>
+#include <QGraphicsScene>
+#include <QGraphicsView>
+#include <QDesktopWidget>
+#include <QtCore/qmath.h>
+#include <QtGui/qevent.h>
+#include <qnumeric.h>
+
+#include <QtDebug>
+
+#if defined(Q_WS_X11)
+# include "private/qt_x11_p.h"
+#endif
+
+
+QT_BEGIN_NAMESPACE
+
+bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event);
+
+//#define QSCROLLER_DEBUG
+
+#ifdef QSCROLLER_DEBUG
+# define qScrollerDebug qDebug
+#else
+# define qScrollerDebug while (false) qDebug
+#endif
+
+QDebug &operator<<(QDebug &dbg, const QScrollerPrivate::ScrollSegment &s)
+{
+ dbg << "\n Time: start:" << s.startTime << " duration:" << s.deltaTime << " stop progress:" << s.stopProgress;
+ dbg << "\n Pos: start:" << s.startPos << " delta:" << s.deltaPos << " stop:" << s.stopPos;
+ dbg << "\n Curve: type:" << s.curve.type() << "\n";
+ return dbg;
+}
+
+
+// a few helper operators to make the code below a lot more readable:
+// otherwise a lot of ifs would have to be multi-line to check both the x
+// and y coordinate separately.
+
+// returns true only if the abs. value of BOTH x and y are <= f
+inline bool operator<=(const QPointF &p, qreal f)
+{
+ return (qAbs(p.x()) <= f) && (qAbs(p.y()) <= f);
+}
+
+// returns true only if the abs. value of BOTH x and y are < f
+inline bool operator<(const QPointF &p, qreal f)
+{
+ return (qAbs(p.x()) < f) && (qAbs(p.y()) < f);
+}
+
+// returns true if the abs. value of EITHER x or y are >= f
+inline bool operator>=(const QPointF &p, qreal f)
+{
+ return (qAbs(p.x()) >= f) || (qAbs(p.y()) >= f);
+}
+
+// returns true if the abs. value of EITHER x or y are > f
+inline bool operator>(const QPointF &p, qreal f)
+{
+ return (qAbs(p.x()) > f) || (qAbs(p.y()) > f);
+}
+
+// returns a new point with both coordinates having the abs. value of the original one
+inline QPointF qAbs(const QPointF &p)
+{
+ return QPointF(qAbs(p.x()), qAbs(p.y()));
+}
+
+// returns a new point with all components of p1 multiplied by the corresponding components of p2
+inline QPointF operator*(const QPointF &p1, const QPointF &p2)
+{
+ return QPointF(p1.x() * p2.x(), p1.y() * p2.y());
+}
+
+// returns a new point with all components of p1 divided by the corresponding components of p2
+inline QPointF operator/(const QPointF &p1, const QPointF &p2)
+{
+ return QPointF(p1.x() / p2.x(), p1.y() / p2.y());
+}
+
+inline QPointF clampToRect(const QPointF &p, const QRectF &rect)
+{
+ qreal x = qBound(rect.left(), p.x(), rect.right());
+ qreal y = qBound(rect.top(), p.y(), rect.bottom());
+ return QPointF(x, y);
+}
+
+// returns -1, 0 or +1 according to r being <0, ==0 or >0
+inline int qSign(qreal r)
+{
+ return (r < 0) ? -1 : ((r > 0) ? 1 : 0);
+}
+
+// this version is not mathematically exact, but it just works for every
+// easing curve type (even custom ones)
+
+static qreal differentialForProgress(const QEasingCurve &curve, qreal pos)
+{
+ const qreal dx = 0.01;
+ qreal left = (pos < qreal(0.5)) ? pos : pos - qreal(dx);
+ qreal right = (pos >= qreal(0.5)) ? pos : pos + qreal(dx);
+ qreal d = (curve.valueForProgress(right) - curve.valueForProgress(left)) / qreal(dx);
+
+ //qScrollerDebug() << "differentialForProgress(type: " << curve.type() << ", pos: " << pos << ") = " << d;
+
+ return d;
+}
+
+// this version is not mathematically exact, but it just works for every
+// easing curve type (even custom ones)
+
+static qreal progressForValue(const QEasingCurve &curve, qreal value)
+{
+ if (curve.type() >= QEasingCurve::InElastic &&
+ curve.type() < QEasingCurve::Custom) {
+ qWarning("progressForValue(): QEasingCurves of type %d do not have an inverse, since they are not injective.", curve.type());
+ return value;
+ }
+ if (value < qreal(0) || value > qreal(1))
+ return value;
+
+ qreal progress = value, left(0), right(1);
+ for (int iterations = 6; iterations; --iterations) {
+ qreal v = curve.valueForProgress(progress);
+ if (v < value)
+ left = progress;
+ else if (v > value)
+ right = progress;
+ else
+ break;
+ progress = (left + right) / qreal(2);
+ }
+ return progress;
+}
+
+
+#ifndef QT_NO_ANIMATION
+class QScrollTimer : public QAbstractAnimation
+{
+public:
+ QScrollTimer(QScrollerPrivate *_d)
+ : d(_d), ignoreUpdate(false), skip(0)
+ { }
+
+ int duration() const
+ {
+ return -1;
+ }
+
+ void start()
+ {
+ // QAbstractAnimation::start() will immediately call
+ // updateCurrentTime(), but our state is not set correctly yet
+ ignoreUpdate = true;
+ QAbstractAnimation::start();
+ ignoreUpdate = false;
+ skip = 0;
+ }
+
+protected:
+ void updateCurrentTime(int /*currentTime*/)
+ {
+ if (!ignoreUpdate) {
+ if (++skip >= d->frameRateSkip()) {
+ skip = 0;
+ d->timerTick();
+ }
+ }
+ }
+
+private:
+ QScrollerPrivate *d;
+ bool ignoreUpdate;
+ int skip;
+};
+#endif // QT_NO_ANIMATION
+
+/*!
+ \class QScroller
+ \brief The QScroller class enables kinetic scrolling for any scrolling widget or graphics item.
+ \since 4.8
+
+ With kinetic scrolling, the user can push the widget in a given
+ direction and it will continue to scroll in this direction until it is
+ stopped either by the user or by friction. Aspects of inertia, friction
+ and other physical concepts can be changed in order to fine-tune an
+ intuitive user experience.
+
+ The QScroller object is the object that stores the current position and
+ scrolling speed and takes care of updates.
+ QScroller can be triggered by a flick gesture
+
+ \code
+ QWidget *w = ...;
+ QScroller::grabGesture(w, QScroller::LeftMouseButtonGesture);
+ \endcode
+
+ or directly like this:
+
+ \code
+ QWidget *w = ...;
+ QScroller *scroller = QScroller::scroller(w);
+ scroller->scrollTo(QPointF(100, 100));
+ \endcode
+
+ The scrolled QObjects receive a QScrollPrepareEvent whenever the scroller needs to
+ update its geometry information and a QScrollEvent whenever the content of the object should
+ actually be scrolled.
+
+ The scroller uses the global QAbstractAnimation timer to generate its QScrollEvents. This
+ can be changed with QScrollerProperties::FrameRate on a per-QScroller basis.
+
+ Several examples in the \c scroller examples directory show how QScroller,
+ QScrollEvent and the scroller gesture can be used.
+
+ Even though this kinetic scroller has a large number of settings available via
+ QScrollerProperties, we recommend that you leave them all at their default, platform optimized
+ values. Before changing them you can experiment with the \c plot example in
+ the \c scroller examples directory.
+
+ \sa QScrollEvent, QScrollPrepareEvent, QScrollerProperties
+*/
+
+typedef QMap<QObject *, QScroller *> ScrollerHash;
+typedef QSet<QScroller *> ScrollerSet;
+
+Q_GLOBAL_STATIC(ScrollerHash, qt_allScrollers)
+Q_GLOBAL_STATIC(ScrollerSet, qt_activeScrollers)
+
+/*!
+ Returns \c true if a QScroller object was already created for \a target; \c false otherwise.
+
+ \sa scroller()
+*/
+bool QScroller::hasScroller(QObject *target)
+{
+ return (qt_allScrollers()->value(target));
+}
+
+/*!
+ Returns the scroller for the given \a target.
+ As long as the object exists this function will always return the same QScroller instance.
+ If no QScroller exists for the \a target, one will implicitly be created.
+ At no point more than one QScroller will be active on an object.
+
+ \sa hasScroller(), target()
+*/
+QScroller *QScroller::scroller(QObject *target)
+{
+ if (!target) {
+ qWarning("QScroller::scroller() was called with a null target.");
+ return 0;
+ }
+
+ if (qt_allScrollers()->contains(target))
+ return qt_allScrollers()->value(target);
+
+ QScroller *s = new QScroller(target);
+ qt_allScrollers()->insert(target, s);
+ return s;
+}
+
+/*!
+ \overload
+ This is the const version of scroller().
+*/
+const QScroller *QScroller::scroller(const QObject *target)
+{
+ return scroller(const_cast<QObject*>(target));
+}
+
+/*!
+ Returns an application wide list of currently active QScroller objects.
+ Active QScroller objects are in a state() that is not QScroller::Inactive.
+ This function is useful when writing your own gesture recognizer.
+*/
+QList<QScroller *> QScroller::activeScrollers()
+{
+ return qt_activeScrollers()->toList();
+}
+
+/*!
+ Returns the target object of this scroller.
+ \sa hasScroller(), scroller()
+ */
+QObject *QScroller::target() const
+{
+ Q_D(const QScroller);
+ return d->target;
+}
+
+/*!
+ \fn QScroller::scrollerPropertiesChanged(const QScrollerProperties &newProperties);
+
+ QScroller emits this signal whenever its scroller properties change.
+ \a newProperties are the new scroller properties.
+
+ \sa scrollerProperties
+*/
+
+
+/*! \property QScroller::scrollerProperties
+ \brief The scroller properties of this scroller.
+ The properties are used by the QScroller to determine its scrolling behavior.
+*/
+QScrollerProperties QScroller::scrollerProperties() const
+{
+ Q_D(const QScroller);
+ return d->properties;
+}
+
+void QScroller::setScrollerProperties(const QScrollerProperties &sp)
+{
+ Q_D(QScroller);
+ if (d->properties != sp) {
+ d->properties = sp;
+ emit scrollerPropertiesChanged(sp);
+
+ // we need to force the recalculation here, since the overshootPolicy may have changed and
+ // existing segments may include an overshoot animation.
+ d->recalcScrollingSegments(true);
+ }
+}
+
+#ifndef QT_NO_GESTURES
+
+/*!
+ Registers a custom scroll gesture recognizer, grabs it for the \a
+ target and returns the resulting gesture type. If \a scrollGestureType is
+ set to TouchGesture the gesture triggers on touch events. If it is set to
+ one of LeftMouseButtonGesture, RightMouseButtonGesture or
+ MiddleMouseButtonGesture it triggers on mouse events of the
+ corresponding button.
+
+ Only one scroll gesture can be active on a single object at the same
+ time. If you call this function twice on the same object, it will
+ ungrab the existing gesture before grabbing the new one.
+
+ \note To avoid unwanted side-effects, mouse events are consumed while
+ the gesture is triggered. Since the initial mouse press event is
+ not consumed, the gesture sends a fake mouse release event
+ at the global position \c{(INT_MIN, INT_MIN)}. This ensures that
+ internal states of the widget that received the original mouse press
+ are consistent.
+
+ \sa ungrabGesture(), grabbedGesture()
+*/
+Qt::GestureType QScroller::grabGesture(QObject *target, ScrollerGestureType scrollGestureType)
+{
+ // ensure that a scroller for target is created
+ QScroller *s = scroller(target);
+ if (!s)
+ return Qt::GestureType(0);
+
+ QScrollerPrivate *sp = s->d_ptr;
+ if (sp->recognizer)
+ ungrabGesture(target); // ungrab the old gesture
+
+ Qt::MouseButton button;
+ switch (scrollGestureType) {
+ case LeftMouseButtonGesture : button = Qt::LeftButton; break;
+ case RightMouseButtonGesture : button = Qt::RightButton; break;
+ case MiddleMouseButtonGesture: button = Qt::MiddleButton; break;
+ default :
+ case TouchGesture : button = Qt::NoButton; break; // NoButton == Touch
+ }
+
+ sp->recognizer = new QFlickGestureRecognizer(button);
+ sp->recognizerType = QGestureRecognizer::registerRecognizer(sp->recognizer);
+
+ if (target->isWidgetType()) {
+ QWidget *widget = static_cast<QWidget *>(target);
+ widget->grabGesture(sp->recognizerType);
+ if (scrollGestureType == TouchGesture)
+ widget->setAttribute(Qt::WA_AcceptTouchEvents);
+#ifndef QT_NO_GRAPHICSVIEW
+ } else if (QGraphicsObject *go = qobject_cast<QGraphicsObject*>(target)) {
+ if (scrollGestureType == TouchGesture)
+ go->setAcceptTouchEvents(true);
+ go->grabGesture(sp->recognizerType);
+#endif // QT_NO_GRAPHICSVIEW
+ }
+ return sp->recognizerType;
+}
+
+/*!
+ Returns the gesture type currently grabbed for the \a target or 0 if no
+ gesture is grabbed.
+
+ \sa grabGesture(), ungrabGesture()
+*/
+Qt::GestureType QScroller::grabbedGesture(QObject *target)
+{
+ QScroller *s = scroller(target);
+ if (s && s->d_ptr)
+ return s->d_ptr->recognizerType;
+ else
+ return Qt::GestureType(0);
+}
+
+/*!
+ Ungrabs the gesture for the \a target.
+ Does nothing if no gesture is grabbed.
+
+ \sa grabGesture(), grabbedGesture()
+*/
+void QScroller::ungrabGesture(QObject *target)
+{
+ QScroller *s = scroller(target);
+ if (!s)
+ return;
+
+ QScrollerPrivate *sp = s->d_ptr;
+ if (!sp->recognizer)
+ return; // nothing to do
+
+ if (target->isWidgetType()) {
+ QWidget *widget = static_cast<QWidget *>(target);
+ widget->ungrabGesture(sp->recognizerType);
+#ifndef QT_NO_GRAPHICSVIEW
+ } else if (QGraphicsObject *go = qobject_cast<QGraphicsObject*>(target)) {
+ go->ungrabGesture(sp->recognizerType);
+#endif
+ }
+
+ QGestureRecognizer::unregisterRecognizer(sp->recognizerType);
+ // do not delete the recognizer. The QGestureManager is doing this.
+ sp->recognizer = 0;
+}
+
+#endif // QT_NO_GESTURES
+
+/*!
+ \internal
+*/
+QScroller::QScroller(QObject *target)
+ : d_ptr(new QScrollerPrivate(this, target))
+{
+ Q_ASSERT(target); // you can't create a scroller without a target in any normal way
+ Q_D(QScroller);
+ d->init();
+}
+
+/*!
+ \internal
+*/
+QScroller::~QScroller()
+{
+ Q_D(QScroller);
+#ifndef QT_NO_GESTURES
+ QGestureRecognizer::unregisterRecognizer(d->recognizerType);
+ // do not delete the recognizer. The QGestureManager is doing this.
+ d->recognizer = 0;
+#endif
+ qt_allScrollers()->remove(d->target);
+ qt_activeScrollers()->remove(this);
+
+ delete d_ptr;
+}
+
+
+/*!
+ \fn QScroller::stateChanged(QScroller::State newState);
+
+ QScroller emits this signal whenever the state changes. \a newState is the new State.
+
+ \sa state
+*/
+
+/*!
+ \property QScroller::state
+ \brief the state of the scroller
+
+ \sa QScroller::State
+*/
+QScroller::State QScroller::state() const
+{
+ Q_D(const QScroller);
+ return d->state;
+}
+
+/*!
+ Stops the scroller and resets its state back to Inactive.
+*/
+void QScroller::stop()
+{
+ Q_D(QScroller);
+ if (d->state != Inactive) {
+ QPointF here = clampToRect(d->contentPosition, d->contentPosRange);
+ qreal snapX = d->nextSnapPos(here.x(), 0, Qt::Horizontal);
+ qreal snapY = d->nextSnapPos(here.y(), 0, Qt::Vertical);
+ QPointF snap = here;
+ if (!qIsNaN(snapX))
+ snap.setX(snapX);
+ if (!qIsNaN(snapY))
+ snap.setY(snapY);
+ d->contentPosition = snap;
+ d->overshootPosition = QPointF(0, 0);
+
+ d->setState(Inactive);
+ }
+}
+
+/*!
+ Returns the pixel per meter metric for the scrolled widget.
+
+ The value is reported for both the x and y axis separately by using a QPointF.
+
+ \note Please note that this value should be physically correct. The actual DPI settings
+ that Qt returns for the display may be reported wrongly on purpose by the underlying
+ windowing system, for example on Mac OS X or Maemo 5.
+*/
+QPointF QScroller::pixelPerMeter() const
+{
+ Q_D(const QScroller);
+ QPointF ppm = d->pixelPerMeter;
+
+#ifndef QT_NO_GRAPHICSVIEW
+ if (QGraphicsObject *go = qobject_cast<QGraphicsObject *>(d->target)) {
+ QTransform viewtr;
+ //TODO: the first view isn't really correct - maybe use an additional field in the prepare event?
+ if (go->scene() && !go->scene()->views().isEmpty())
+ viewtr = go->scene()->views().first()->viewportTransform();
+ QTransform tr = go->deviceTransform(viewtr);
+ if (tr.isScaling()) {
+ QPointF p0 = tr.map(QPointF(0, 0));
+ QPointF px = tr.map(QPointF(1, 0));
+ QPointF py = tr.map(QPointF(0, 1));
+ ppm.rx() /= QLineF(p0, px).length();
+ ppm.ry() /= QLineF(p0, py).length();
+ }
+ }
+#endif // QT_NO_GRAPHICSVIEW
+ return ppm;
+}
+
+/*!
+ Returns the current scrolling velocity in meter per second when the state is Scrolling or Dragging.
+ Returns a zero velocity otherwise.
+
+ The velocity is reported for both the x and y axis separately by using a QPointF.
+
+ \sa pixelPerMeter()
+*/
+QPointF QScroller::velocity() const
+{
+ Q_D(const QScroller);
+ const QScrollerPropertiesPrivate *sp = d->properties.d.data();
+
+ switch (state()) {
+ case Dragging:
+ return d->releaseVelocity;
+ case Scrolling: {
+ QPointF vel;
+ qint64 now = d->monotonicTimer.elapsed();
+
+ if (!d->xSegments.isEmpty()) {
+ const QScrollerPrivate::ScrollSegment &s = d->xSegments.head();
+ qreal progress = qreal(now - s.startTime) / qreal(s.deltaTime);
+ qreal v = qSign(s.deltaPos) * qreal(s.deltaTime) / qreal(1000) * sp->decelerationFactor * qreal(0.5) * differentialForProgress(s.curve, progress);
+ vel.setX(v);
+ }
+
+ if (!d->ySegments.isEmpty()) {
+ const QScrollerPrivate::ScrollSegment &s = d->ySegments.head();
+ qreal progress = qreal(now - s.startTime) / qreal(s.deltaTime);
+ qreal v = qSign(s.deltaPos) * qreal(s.deltaTime) / qreal(1000) * sp->decelerationFactor * qreal(0.5) * differentialForProgress(s.curve, progress);
+ vel.setY(v);
+ }
+ return vel;
+ }
+ default:
+ return QPointF(0, 0);
+ }
+}
+
+/*!
+ Returns the estimated final position for the current scroll movement.
+ Returns the current position if the scroller state is not Scrolling.
+ The result is undefined when the scroller state is Inactive.
+
+ The target position is in pixel.
+
+ \sa pixelPerMeter(), scrollTo()
+*/
+QPointF QScroller::finalPosition() const
+{
+ Q_D(const QScroller);
+ return QPointF(d->scrollingSegmentsEndPos(Qt::Horizontal),
+ d->scrollingSegmentsEndPos(Qt::Vertical));
+}
+
+/*!
+ Starts scrolling the widget so that point \a pos is at the top-left position in
+ the viewport.
+
+ The behaviour when scrolling outside the valid scroll area is undefined.
+ In this case the scroller might or might not overshoot.
+
+ The scrolling speed will be calculated so that the given position will
+ be reached after a platform-defined time span.
+
+ \a pos is given in viewport coordinates.
+
+ \sa ensureVisible()
+*/
+void QScroller::scrollTo(const QPointF &pos)
+{
+ // we could make this adjustable via QScrollerProperties
+ scrollTo(pos, 300);
+}
+
+/*! \overload
+
+ This version will reach its destination position in \a scrollTime milliseconds.
+*/
+void QScroller::scrollTo(const QPointF &pos, int scrollTime)
+{
+ Q_D(QScroller);
+
+ if (d->state == Pressed || d->state == Dragging )
+ return;
+
+ // no need to resend a prepare event if we are already scrolling
+ if (d->state == Inactive && !d->prepareScrolling(QPointF()))
+ return;
+
+ QPointF newpos = clampToRect(pos, d->contentPosRange);
+ qreal snapX = d->nextSnapPos(newpos.x(), 0, Qt::Horizontal);
+ qreal snapY = d->nextSnapPos(newpos.y(), 0, Qt::Vertical);
+ if (!qIsNaN(snapX))
+ newpos.setX(snapX);
+ if (!qIsNaN(snapY))
+ newpos.setY(snapY);
+
+ qScrollerDebug() << "QScroller::scrollTo(req:" << pos << " [pix] / snap:" << newpos << ", " << scrollTime << " [ms])";
+
+ if (newpos == d->contentPosition + d->overshootPosition)
+ return;
+
+ QPointF vel = velocity();
+
+ if (scrollTime < 0)
+ scrollTime = 0;
+ qreal time = qreal(scrollTime) / 1000;
+
+ d->createScrollToSegments(vel.x(), time, newpos.x(), Qt::Horizontal, QScrollerPrivate::ScrollTypeScrollTo);
+ d->createScrollToSegments(vel.y(), time, newpos.y(), Qt::Vertical, QScrollerPrivate::ScrollTypeScrollTo);
+
+ if (!scrollTime)
+ d->setContentPositionHelperScrolling();
+ d->setState(scrollTime ? Scrolling : Inactive);
+}
+
+/*!
+ Starts scrolling so that the rectangle \a rect is visible inside the
+ viewport with additional margins specified in pixels by \a xmargin and \a ymargin around
+ the rect.
+
+ In cases where it is not possible to fit the rect plus margins inside the viewport the contents
+ are scrolled so that as much as possible is visible from \a rect.
+
+ The scrolling speed is calculated so that the given position is reached after a platform-defined
+ time span.
+
+ This function performs the actual scrolling by calling scrollTo().
+
+ \sa scrollTo()
+*/
+void QScroller::ensureVisible(const QRectF &rect, qreal xmargin, qreal ymargin)
+{
+ // we could make this adjustable via QScrollerProperties
+ ensureVisible(rect, xmargin, ymargin, 1000);
+}
+
+/*! \overload
+
+ This version will reach its destination position in \a scrollTime milliseconds.
+*/
+void QScroller::ensureVisible(const QRectF &rect, qreal xmargin, qreal ymargin, int scrollTime)
+{
+ Q_D(QScroller);
+
+ if (d->state == Pressed || d->state == Dragging )
+ return;
+
+ if (d->state == Inactive && !d->prepareScrolling(QPointF()))
+ return;
+
+ // -- calculate the current pos (or the position after the current scroll)
+ QPointF startPos = d->contentPosition + d->overshootPosition;
+ startPos = QPointF(d->scrollingSegmentsEndPos(Qt::Horizontal),
+ d->scrollingSegmentsEndPos(Qt::Vertical));
+
+ QRectF marginRect(rect.x() - xmargin, rect.y() - ymargin,
+ rect.width() + 2 * xmargin, rect.height() + 2 * ymargin);
+
+ QSizeF visible = d->viewportSize;
+ QRectF visibleRect(startPos, visible);
+
+ qScrollerDebug() << "QScroller::ensureVisible(" << rect << " [pix], " << xmargin << " [pix], " << ymargin << " [pix], " << scrollTime << "[ms])";
+ qScrollerDebug() << " --> content position:" << d->contentPosition;
+
+ if (visibleRect.contains(marginRect))
+ return;
+
+ QPointF newPos = startPos;
+
+ if (visibleRect.width() < rect.width()) {
+ // at least try to move the rect into view
+ if (rect.left() > visibleRect.left())
+ newPos.setX(rect.left());
+ else if (rect.right() < visibleRect.right())
+ newPos.setX(rect.right() - visible.width());
+
+ } else if (visibleRect.width() < marginRect.width()) {
+ newPos.setX(rect.center().x() - visibleRect.width() / 2);
+ } else if (marginRect.left() > visibleRect.left()) {
+ newPos.setX(marginRect.left());
+ } else if (marginRect.right() < visibleRect.right()) {
+ newPos.setX(marginRect.right() - visible.width());
+ }
+
+ if (visibleRect.height() < rect.height()) {
+ // at least try to move the rect into view
+ if (rect.top() > visibleRect.top())
+ newPos.setX(rect.top());
+ else if (rect.bottom() < visibleRect.bottom())
+ newPos.setX(rect.bottom() - visible.height());
+
+ } else if (visibleRect.height() < marginRect.height()) {
+ newPos.setY(rect.center().y() - visibleRect.height() / 2);
+ } else if (marginRect.top() > visibleRect.top()) {
+ newPos.setY(marginRect.top());
+ } else if (marginRect.bottom() < visibleRect.bottom()) {
+ newPos.setY(marginRect.bottom() - visible.height());
+ }
+
+ // clamp to maximum content position
+ newPos = clampToRect(newPos, d->contentPosRange);
+ if (newPos == startPos)
+ return;
+
+ scrollTo(newPos, scrollTime);
+}
+
+/*! This function resends the QScrollPrepareEvent.
+ Calling resendPrepareEvent triggers a QScrollPrepareEvent from the scroller.
+ This allows the receiver to re-set content position and content size while
+ scrolling.
+ Calling this function while in the Inactive state is useless as the prepare event
+ is sent again before scrolling starts.
+ */
+void QScroller::resendPrepareEvent()
+{
+ Q_D(QScroller);
+ d->prepareScrolling(d->pressPosition);
+}
+
+/*! Set the snap positions for the horizontal axis to a list of \a positions.
+ This overwrites all previously set snap positions and also a previously
+ set snapping interval.
+ Snapping can be deactivated by setting an empty list of positions.
+ */
+void QScroller::setSnapPositionsX(const QList<qreal> &positions)
+{
+ Q_D(QScroller);
+ d->snapPositionsX = positions;
+ d->snapIntervalX = 0.0;
+
+ d->recalcScrollingSegments();
+}
+
+/*! Set the snap positions for the horizontal axis to regular spaced intervals.
+ The first snap position is at \a first. The next at \a first + \a interval.
+ This can be used to implement a list header.
+ This overwrites all previously set snap positions and also a previously
+ set snapping interval.
+ Snapping can be deactivated by setting an interval of 0.0
+ */
+void QScroller::setSnapPositionsX(qreal first, qreal interval)
+{
+ Q_D(QScroller);
+ d->snapFirstX = first;
+ d->snapIntervalX = interval;
+ d->snapPositionsX.clear();
+
+ d->recalcScrollingSegments();
+}
+
+/*! Set the snap positions for the vertical axis to a list of \a positions.
+ This overwrites all previously set snap positions and also a previously
+ set snapping interval.
+ Snapping can be deactivated by setting an empty list of positions.
+ */
+void QScroller::setSnapPositionsY(const QList<qreal> &positions)
+{
+ Q_D(QScroller);
+ d->snapPositionsY = positions;
+ d->snapIntervalY = 0.0;
+
+ d->recalcScrollingSegments();
+}
+
+/*! Set the snap positions for the vertical axis to regular spaced intervals.
+ The first snap position is at \a first. The next at \a first + \a interval.
+ This overwrites all previously set snap positions and also a previously
+ set snapping interval.
+ Snapping can be deactivated by setting an interval of 0.0
+ */
+void QScroller::setSnapPositionsY(qreal first, qreal interval)
+{
+ Q_D(QScroller);
+ d->snapFirstY = first;
+ d->snapIntervalY = interval;
+ d->snapPositionsY.clear();
+
+ d->recalcScrollingSegments();
+}
+
+
+
+// -------------- private ------------
+
+QScrollerPrivate::QScrollerPrivate(QScroller *q, QObject *_target)
+ : target(_target)
+#ifndef QT_NO_GESTURES
+ , recognizer(0)
+ , recognizerType(Qt::CustomGesture)
+#endif
+ , state(QScroller::Inactive)
+ , firstScroll(true)
+ , pressTimestamp(0)
+ , lastTimestamp(0)
+ , snapFirstX(-1.0)
+ , snapIntervalX(0.0)
+ , snapFirstY(-1.0)
+ , snapIntervalY(0.0)
+#ifndef QT_NO_ANIMATION
+ , scrollTimer(new QScrollTimer(this))
+#endif
+ , q_ptr(q)
+{
+ connect(target, SIGNAL(destroyed(QObject*)), this, SLOT(targetDestroyed()));
+}
+
+void QScrollerPrivate::init()
+{
+ setDpiFromWidget(0);
+ monotonicTimer.start();
+}
+
+void QScrollerPrivate::sendEvent(QObject *o, QEvent *e)
+{
+ qt_sendSpontaneousEvent(o, e);
+}
+
+const char *QScrollerPrivate::stateName(QScroller::State state)
+{
+ switch (state) {
+ case QScroller::Inactive: return "inactive";
+ case QScroller::Pressed: return "pressed";
+ case QScroller::Dragging: return "dragging";
+ case QScroller::Scrolling: return "scrolling";
+ default: return "(invalid)";
+ }
+}
+
+const char *QScrollerPrivate::inputName(QScroller::Input input)
+{
+ switch (input) {
+ case QScroller::InputPress: return "press";
+ case QScroller::InputMove: return "move";
+ case QScroller::InputRelease: return "release";
+ default: return "(invalid)";
+ }
+}
+
+void QScrollerPrivate::targetDestroyed()
+{
+#ifndef QT_NO_ANIMATION
+ scrollTimer->stop();
+#endif
+ delete q_ptr;
+}
+
+void QScrollerPrivate::timerTick()
+{
+ struct timerevent {
+ QScroller::State state;
+ typedef void (QScrollerPrivate::*timerhandler_t)();
+ timerhandler_t handler;
+ };
+
+ timerevent timerevents[] = {
+ { QScroller::Dragging, &QScrollerPrivate::timerEventWhileDragging },
+ { QScroller::Scrolling, &QScrollerPrivate::timerEventWhileScrolling },
+ };
+
+ for (int i = 0; i < int(sizeof(timerevents) / sizeof(*timerevents)); ++i) {
+ timerevent *te = timerevents + i;
+
+ if (state == te->state) {
+ (this->*te->handler)();
+ return;
+ }
+ }
+
+#ifndef QT_NO_ANIMATION
+ scrollTimer->stop();
+#endif
+}
+
+/*!
+ This function is used by gesture recognizers to inform the scroller about a new input event.
+ The scroller changes its internal state() according to the input event and its attached
+ scroller properties. The scroller doesn't distinguish between the kind of input device the
+ event came from. Therefore the event needs to be split into the \a input type, a \a position and a
+ milli-second \a timestamp. The \a position needs to be in the target's coordinate system.
+
+ The return value is \c true if the event should be consumed by the calling filter or \c false
+ if the event should be forwarded to the control.
+
+ \note Using grabGesture() should be sufficient for most use cases.
+*/
+bool QScroller::handleInput(Input input, const QPointF &position, qint64 timestamp)
+{
+ Q_D(QScroller);
+
+ qScrollerDebug() << "QScroller::handleInput(" << input << ", " << d->stateName(d->state) << ", " << position << ", " << timestamp << ")";
+ struct statechange {
+ State state;
+ Input input;
+ typedef bool (QScrollerPrivate::*inputhandler_t)(const QPointF &position, qint64 timestamp);
+ inputhandler_t handler;
+ };
+
+ statechange statechanges[] = {
+ { QScroller::Inactive, InputPress, &QScrollerPrivate::pressWhileInactive },
+ { QScroller::Pressed, InputMove, &QScrollerPrivate::moveWhilePressed },
+ { QScroller::Pressed, InputRelease, &QScrollerPrivate::releaseWhilePressed },
+ { QScroller::Dragging, InputMove, &QScrollerPrivate::moveWhileDragging },
+ { QScroller::Dragging, InputRelease, &QScrollerPrivate::releaseWhileDragging },
+ { QScroller::Scrolling, InputPress, &QScrollerPrivate::pressWhileScrolling }
+ };
+
+ for (int i = 0; i < int(sizeof(statechanges) / sizeof(*statechanges)); ++i) {
+ statechange *sc = statechanges + i;
+
+ if (d->state == sc->state && input == sc->input)
+ return (d->*sc->handler)(position - d->overshootPosition, timestamp);
+ }
+ return false;
+}
+
+#if !defined(Q_WS_MAC)
+// the Mac version is implemented in qscroller_mac.mm
+
+QPointF QScrollerPrivate::realDpi(int screen)
+{
+# ifdef Q_WS_MAEMO_5
+ Q_UNUSED(screen);
+
+ // The DPI value is hardcoded to 96 on Maemo5:
+ // https://projects.maemo.org/bugzilla/show_bug.cgi?id=152525
+ // This value (260) is only correct for the N900 though, but
+ // there's no way to get the real DPI at run time.
+ return QPointF(260, 260);
+
+# elif defined(Q_WS_X11) && !defined(QT_NO_XRANDR)
+ if (X11 && X11->use_xrandr && X11->ptrXRRSizes && X11->ptrXRRRootToScreen) {
+ int nsizes = 0;
+ // QDesktopWidget is based on Xinerama screens, which do not always
+ // correspond to RandR screens: NVidia's TwinView e.g. will show up
+ // as 2 screens in QDesktopWidget, but libXRandR will only see 1 screen.
+ // (although with the combined size of the Xinerama screens).
+ // Additionally, libXrandr will simply crash when calling XRRSizes
+ // for (the non-existent) screen 1 in this scenario.
+ Window root = RootWindow(X11->display, screen == -1 ? X11->defaultScreen : screen);
+ int randrscreen = (root != XNone) ? X11->ptrXRRRootToScreen(X11->display, root) : -1;
+
+ XRRScreenSize *sizes = X11->ptrXRRSizes(X11->display, randrscreen == -1 ? 0 : randrscreen, &nsizes);
+ if (nsizes > 0 && sizes && sizes->width && sizes->height && sizes->mwidth && sizes->mheight) {
+ qScrollerDebug() << "XRandR DPI:" << QPointF(qreal(25.4) * qreal(sizes->width) / qreal(sizes->mwidth),
+ qreal(25.4) * qreal(sizes->height) / qreal(sizes->mheight));
+ return QPointF(qreal(25.4) * qreal(sizes->width) / qreal(sizes->mwidth),
+ qreal(25.4) * qreal(sizes->height) / qreal(sizes->mheight));
+ }
+ }
+# endif
+
+ QWidget *w = QApplication::desktop()->screen(screen);
+ return QPointF(w->physicalDpiX(), w->physicalDpiY());
+}
+
+#endif // !Q_WS_MAC
+
+
+/*! \internal
+ Returns the resolution of the used screen.
+*/
+QPointF QScrollerPrivate::dpi() const
+{
+ return pixelPerMeter * qreal(0.0254);
+}
+
+/*! \internal
+ Sets the resolution used for scrolling.
+ This resolution is only used by the kinetic scroller. If you change this
+ then the scroller will behave quite different as a lot of the values are
+ given in physical distances (millimeter).
+*/
+void QScrollerPrivate::setDpi(const QPointF &dpi)
+{
+ pixelPerMeter = dpi / qreal(0.0254);
+}
+
+/*! \internal
+ Sets the dpi used for scrolling to the value of the widget.
+*/
+void QScrollerPrivate::setDpiFromWidget(QWidget *widget)
+{
+ QDesktopWidget *dw = QApplication::desktop();
+ setDpi(realDpi(widget ? dw->screenNumber(widget) : dw->primaryScreen()));
+}
+
+/*! \internal
+ Updates the velocity during dragging.
+ Sets releaseVelocity.
+*/
+void QScrollerPrivate::updateVelocity(const QPointF &deltaPixelRaw, qint64 deltaTime)
+{
+ if (deltaTime <= 0)
+ return;
+
+ Q_Q(QScroller);
+ QPointF ppm = q->pixelPerMeter();
+ const QScrollerPropertiesPrivate *sp = properties.d.data();
+ QPointF deltaPixel = deltaPixelRaw;
+
+ qScrollerDebug() << "QScroller::updateVelocity(" << deltaPixelRaw << " [delta pix], " << deltaTime << " [delta ms])";
+
+ // faster than 2.5mm/ms seems bogus (that would be a screen height in ~20 ms)
+ if (((deltaPixelRaw / qreal(deltaTime)).manhattanLength() / ((ppm.x() + ppm.y()) / 2) * 1000) > qreal(2.5))
+ deltaPixel = deltaPixelRaw * qreal(2.5) * ppm / 1000 / (deltaPixelRaw / qreal(deltaTime)).manhattanLength();
+
+ QPointF newv = -deltaPixel / qreal(deltaTime) * qreal(1000) / ppm;
+ // around 95% of all updates are in the [1..50] ms range, so make sure
+ // to scale the smoothing factor over that range: this way a 50ms update
+ // will have full impact, while 5ms update will only have a 10% impact.
+ qreal smoothing = sp->dragVelocitySmoothingFactor * qMin(qreal(deltaTime), qreal(50)) / qreal(50);
+
+ // only smooth if we already have a release velocity and only if the
+ // user hasn't stopped to move his finger for more than 100ms
+ if ((releaseVelocity != QPointF(0, 0)) && (deltaTime < 100)) {
+ qScrollerDebug() << "SMOOTHED from " << newv << " to " << newv * smoothing + releaseVelocity * (qreal(1) - smoothing);
+ // smooth x or y only if the new velocity is either 0 or at least in
+ // the same direction of the release velocity
+ if (!newv.x() || (qSign(releaseVelocity.x()) == qSign(newv.x())))
+ newv.setX(newv.x() * smoothing + releaseVelocity.x() * (qreal(1) - smoothing));
+ if (!newv.y() || (qSign(releaseVelocity.y()) == qSign(newv.y())))
+ newv.setY(newv.y() * smoothing + releaseVelocity.y() * (qreal(1) - smoothing));
+ } else
+ qScrollerDebug() << "NO SMOOTHING to " << newv;
+
+ releaseVelocity.setX(qBound(-sp->maximumVelocity, newv.x(), sp->maximumVelocity));
+ releaseVelocity.setY(qBound(-sp->maximumVelocity, newv.y(), sp->maximumVelocity));
+
+ qScrollerDebug() << " --> new velocity:" << releaseVelocity;
+}
+
+void QScrollerPrivate::pushSegment(ScrollType type, qreal deltaTime, qreal stopProgress, qreal startPos, qreal deltaPos, qreal stopPos, QEasingCurve::Type curve, Qt::Orientation orientation)
+{
+ if (startPos == stopPos || deltaPos == 0)
+ return;
+
+ ScrollSegment s;
+ if (orientation == Qt::Horizontal && !xSegments.isEmpty())
+ s.startTime = xSegments.last().startTime + xSegments.last().deltaTime * xSegments.last().stopProgress;
+ else if (orientation == Qt::Vertical && !ySegments.isEmpty())
+ s.startTime = ySegments.last().startTime + ySegments.last().deltaTime * ySegments.last().stopProgress;
+ else
+ s.startTime = monotonicTimer.elapsed();
+
+ s.startPos = startPos;
+ s.deltaPos = deltaPos;
+ s.stopPos = stopPos;
+ s.deltaTime = deltaTime * 1000;
+ s.stopProgress = stopProgress;
+ s.curve.setType(curve);
+ s.type = type;
+
+ if (orientation == Qt::Horizontal)
+ xSegments.enqueue(s);
+ else
+ ySegments.enqueue(s);
+
+ qScrollerDebug() << "+++ Added a new ScrollSegment: " << s;
+}
+
+
+/*! \internal
+ Clears the old segments and recalculates them if the current segments are not longer valid
+*/
+void QScrollerPrivate::recalcScrollingSegments(bool forceRecalc)
+{
+ Q_Q(QScroller);
+ QPointF ppm = q->pixelPerMeter();
+
+ releaseVelocity = q->velocity();
+
+ if (forceRecalc || !scrollingSegmentsValid(Qt::Horizontal))
+ createScrollingSegments(releaseVelocity.x(), contentPosition.x() + overshootPosition.x(), ppm.x(), Qt::Horizontal);
+
+ if (forceRecalc || !scrollingSegmentsValid(Qt::Vertical))
+ createScrollingSegments(releaseVelocity.y(), contentPosition.y() + overshootPosition.y(), ppm.y(), Qt::Vertical);
+}
+
+/*! \internal
+ Returns the end position after the current scroll has finished.
+*/
+qreal QScrollerPrivate::scrollingSegmentsEndPos(Qt::Orientation orientation) const
+{
+ if (orientation == Qt::Horizontal) {
+ if (xSegments.isEmpty())
+ return contentPosition.x() + overshootPosition.x();
+ else
+ return xSegments.last().stopPos;
+ } else {
+ if (ySegments.isEmpty())
+ return contentPosition.y() + overshootPosition.y();
+ else
+ return ySegments.last().stopPos;
+ }
+}
+
+/*! \internal
+ Checks if the scroller segment end in a valid position.
+*/
+bool QScrollerPrivate::scrollingSegmentsValid(Qt::Orientation orientation)
+{
+ QQueue<ScrollSegment> *segments;
+ qreal minPos;
+ qreal maxPos;
+
+ if (orientation == Qt::Horizontal) {
+ segments = &xSegments;
+ minPos = contentPosRange.left();
+ maxPos = contentPosRange.right();
+ } else {
+ segments = &ySegments;
+ minPos = contentPosRange.top();
+ maxPos = contentPosRange.bottom();
+ }
+
+ if (segments->isEmpty())
+ return true;
+
+ const ScrollSegment &last = segments->last();
+ qreal stopPos = last.stopPos;
+
+ if (last.type == ScrollTypeScrollTo)
+ return true; // scrollTo is always valid
+
+ if (last.type == ScrollTypeOvershoot &&
+ (stopPos != minPos && stopPos != maxPos))
+ return false;
+
+ if (stopPos < minPos || stopPos > maxPos)
+ return false;
+
+ if (stopPos == minPos || stopPos == maxPos) // the begin and the end of the list are always ok
+ return true;
+
+ qreal nextSnap = nextSnapPos(stopPos, 0, orientation);
+ if (!qIsNaN(nextSnap) && stopPos != nextSnap)
+ return false;
+
+ return true;
+}
+
+/*! \internal
+ Creates the sections needed to scroll to the specific \a endPos to the segments queue.
+*/
+void QScrollerPrivate::createScrollToSegments(qreal v, qreal deltaTime, qreal endPos, Qt::Orientation orientation, ScrollType type)
+{
+ Q_UNUSED(v);
+
+ if (orientation == Qt::Horizontal)
+ xSegments.clear();
+ else
+ ySegments.clear();
+
+ qScrollerDebug() << "+++ createScrollToSegments: t:" << deltaTime << "ep:" << endPos << "o:" << int(orientation);
+
+ const QScrollerPropertiesPrivate *sp = properties.d.data();
+
+ qreal startPos = (orientation == Qt::Horizontal) ? contentPosition.x() + overshootPosition.x()
+ : contentPosition.y() + overshootPosition.y();
+ qreal deltaPos = (endPos - startPos) / 2;
+
+ pushSegment(type, deltaTime * qreal(0.3), qreal(1.0), startPos, deltaPos, startPos + deltaPos, QEasingCurve::InQuad, orientation);
+ pushSegment(type, deltaTime * qreal(0.7), qreal(1.0), startPos + deltaPos, deltaPos, endPos, sp->scrollingCurve.type(), orientation);
+}
+
+/*! \internal
+*/
+void QScrollerPrivate::createScrollingSegments(qreal v, qreal startPos, qreal ppm, Qt::Orientation orientation)
+{
+ const QScrollerPropertiesPrivate *sp = properties.d.data();
+
+ QScrollerProperties::OvershootPolicy policy;
+ qreal minPos;
+ qreal maxPos;
+ qreal viewSize;
+
+ if (orientation == Qt::Horizontal) {
+ xSegments.clear();
+ policy = sp->hOvershootPolicy;
+ minPos = contentPosRange.left();
+ maxPos = contentPosRange.right();
+ viewSize = viewportSize.width();
+ } else {
+ ySegments.clear();
+ policy = sp->vOvershootPolicy;
+ minPos = contentPosRange.top();
+ maxPos = contentPosRange.bottom();
+ viewSize = viewportSize.height();
+ }
+
+ bool alwaysOvershoot = (policy == QScrollerProperties::OvershootAlwaysOn);
+ bool noOvershoot = (policy == QScrollerProperties::OvershootAlwaysOff) || !sp->overshootScrollDistanceFactor;
+ bool canOvershoot = !noOvershoot && (alwaysOvershoot || maxPos);
+
+ qScrollerDebug() << "+++ createScrollingSegments: s:" << startPos << "maxPos:" << maxPos << "o:" << int(orientation);
+
+ qScrollerDebug() << "v = " << v << ", decelerationFactor = " << sp->decelerationFactor << ", curveType = " << sp->scrollingCurve.type();
+
+ // This is only correct for QEasingCurve::OutQuad (linear velocity,
+ // constant deceleration), but the results look and feel ok for OutExpo
+ // and OutSine as well
+
+ // v(t) = deltaTime * a * 0.5 * differentialForProgress(t / deltaTime)
+ // v(0) = vrelease
+ // v(deltaTime) = 0
+ // deltaTime = (2 * vrelease) / (a * differntial(0))
+
+ // pos(t) = integrate(v(t)dt)
+ // pos(t) = vrelease * t - 0.5 * a * t * t
+ // pos(t) = deltaTime * a * 0.5 * progress(t / deltaTime) * deltaTime
+ // deltaPos = pos(deltaTime)
+
+ qreal deltaTime = (qreal(2) * qAbs(v)) / (sp->decelerationFactor * differentialForProgress(sp->scrollingCurve, 0));
+ qreal deltaPos = qSign(v) * deltaTime * deltaTime * qreal(0.5) * sp->decelerationFactor * ppm;
+ qreal endPos = startPos + deltaPos;
+
+ qScrollerDebug() << " Real Delta:" << deltaPos;
+
+ // -- determine snap points
+ qreal nextSnap = nextSnapPos(endPos, 0, orientation);
+ qreal lowerSnapPos = nextSnapPos(startPos, -1, orientation);
+ qreal higherSnapPos = nextSnapPos(startPos, 1, orientation);
+
+ qScrollerDebug() << " Real Delta:" << lowerSnapPos <<"-"<<nextSnap <<"-"<<higherSnapPos;
+
+ // - check if we can reach another snap point
+ if (nextSnap > higherSnapPos || qIsNaN(higherSnapPos))
+ higherSnapPos = nextSnap;
+ if (nextSnap < lowerSnapPos || qIsNaN(lowerSnapPos))
+ lowerSnapPos = nextSnap;
+
+ // -- check if are in overshoot and end in overshoot
+ if ((startPos < minPos && endPos < minPos) ||
+ (startPos > maxPos && endPos > maxPos)) {
+ qreal stopPos = endPos < minPos ? minPos : maxPos;
+ qreal oDeltaTime = sp->overshootScrollTime;
+
+ pushSegment(ScrollTypeOvershoot, oDeltaTime * qreal(0.7), qreal(1.0), startPos, stopPos - startPos, stopPos, sp->scrollingCurve.type(), orientation);
+ return;
+ }
+
+ if (qAbs(v) < sp->minimumVelocity) {
+
+ qScrollerDebug() << "### below minimum Vel" << orientation;
+
+ // - no snap points or already at one
+ if (qIsNaN(nextSnap) || nextSnap == startPos)
+ return; // nothing to do, no scrolling needed.
+
+ // - decide which point to use
+
+ qreal snapDistance = higherSnapPos - lowerSnapPos;
+
+ qreal pressDistance = (orientation == Qt::Horizontal) ?
+ lastPosition.x() - pressPosition.x() :
+ lastPosition.y() - pressPosition.y();
+
+ // if not dragged far enough, pick the next snap point.
+ if (sp->snapPositionRatio == 0.0 || qAbs(pressDistance / sp->snapPositionRatio) > snapDistance)
+ endPos = nextSnap;
+ else if (pressDistance < 0.0)
+ endPos = lowerSnapPos;
+ else
+ endPos = higherSnapPos;
+
+ deltaPos = endPos - startPos;
+ qreal midPos = startPos + deltaPos * qreal(0.3);
+ pushSegment(ScrollTypeFlick, sp->snapTime * qreal(0.3), qreal(1.0), startPos, midPos - startPos, midPos, QEasingCurve::InQuad, orientation);
+ pushSegment(ScrollTypeFlick, sp->snapTime * qreal(0.7), qreal(1.0), midPos, endPos - midPos, endPos, sp->scrollingCurve.type(), orientation);
+ return;
+ }
+
+ // - go to the next snappoint if there is one
+ if (v > 0 && !qIsNaN(higherSnapPos)) {
+ // change the time in relation to the changed end position
+ if (endPos - startPos)
+ deltaTime *= qAbs((higherSnapPos - startPos) / (endPos - startPos));
+ if (deltaTime > sp->snapTime)
+ deltaTime = sp->snapTime;
+ endPos = higherSnapPos;
+
+ } else if (v < 0 && !qIsNaN(lowerSnapPos)) {
+ // change the time in relation to the changed end position
+ if (endPos - startPos)
+ deltaTime *= qAbs((lowerSnapPos - startPos) / (endPos - startPos));
+ if (deltaTime > sp->snapTime)
+ deltaTime = sp->snapTime;
+ endPos = lowerSnapPos;
+
+ // -- check if we are overshooting
+ } else if (endPos < minPos || endPos > maxPos) {
+ qreal stopPos = endPos < minPos ? minPos : maxPos;
+
+ qScrollerDebug() << "Overshoot: delta:" << (stopPos - startPos);
+
+ qreal stopProgress = progressForValue(sp->scrollingCurve, qAbs((stopPos - startPos) / deltaPos));
+
+ if (!canOvershoot) {
+ qScrollerDebug() << "Overshoot stopp:" << stopProgress;
+
+ pushSegment(ScrollTypeFlick, deltaTime, stopProgress, startPos, endPos, stopPos, sp->scrollingCurve.type(), orientation);
+ } else {
+ qreal oDeltaTime = sp->overshootScrollTime;
+ qreal oStopProgress = qMin(stopProgress + oDeltaTime * qreal(0.3) / deltaTime, qreal(1));
+ qreal oDistance = startPos + deltaPos * sp->scrollingCurve.valueForProgress(oStopProgress) - stopPos;
+ qreal oMaxDistance = qSign(oDistance) * (viewSize * sp->overshootScrollDistanceFactor);
+
+ qScrollerDebug() << "1 oDistance:" << oDistance << "Max:" << oMaxDistance << "stopP/oStopP" << stopProgress << oStopProgress;
+
+ if (qAbs(oDistance) > qAbs(oMaxDistance)) {
+ oStopProgress = progressForValue(sp->scrollingCurve, qAbs((stopPos + oMaxDistance - startPos) / deltaPos));
+ oDistance = oMaxDistance;
+ qScrollerDebug() << "2 oDistance:" << oDistance << "Max:" << oMaxDistance << "stopP/oStopP" << stopProgress << oStopProgress;
+ }
+
+ pushSegment(ScrollTypeFlick, deltaTime, oStopProgress, startPos, deltaPos, stopPos + oDistance, sp->scrollingCurve.type(), orientation);
+ pushSegment(ScrollTypeOvershoot, oDeltaTime * qreal(0.7), qreal(1.0), stopPos + oDistance, -oDistance, stopPos, sp->scrollingCurve.type(), orientation);
+ }
+ return;
+ }
+
+ pushSegment(ScrollTypeFlick, deltaTime, qreal(1.0), startPos, deltaPos, endPos, sp->scrollingCurve.type(), orientation);
+}
+
+
+/*! \internal
+ Prepares scrolling by sending a QScrollPrepareEvent to the receiver widget.
+ Returns true if the scrolling was accepted and a target was returned.
+*/
+bool QScrollerPrivate::prepareScrolling(const QPointF &position)
+{
+ QScrollPrepareEvent spe(position);
+ spe.ignore();
+ sendEvent(target, &spe);
+
+ qScrollerDebug() << "QScrollPrepareEvent returned from" << target << "with" << spe.isAccepted() << "mcp:" << spe.contentPosRange() << "cp:" << spe.contentPos();
+ if (spe.isAccepted()) {
+ QPointF oldContentPos = contentPosition + overshootPosition;
+ QPointF contentDelta = spe.contentPos() - oldContentPos;
+
+ viewportSize = spe.viewportSize();
+ contentPosRange = spe.contentPosRange();
+ if (contentPosRange.width() < 0)
+ contentPosRange.setWidth(0);
+ if (contentPosRange.height() < 0)
+ contentPosRange.setHeight(0);
+ contentPosition = clampToRect(spe.contentPos(), contentPosRange);
+ overshootPosition = spe.contentPos() - contentPosition;
+
+ // - check if the content position was moved
+ if (contentDelta != QPointF(0, 0)) {
+ // need to correct all segments
+ for (int i = 0; i < xSegments.count(); i++)
+ xSegments[i].startPos -= contentDelta.x();
+
+ for (int i = 0; i < ySegments.count(); i++)
+ ySegments[i].startPos -= contentDelta.y();
+ }
+
+ if (QWidget *w = qobject_cast<QWidget *>(target))
+ setDpiFromWidget(w);
+#ifndef QT_NO_GRAPHICSVIEW
+ if (QGraphicsObject *go = qobject_cast<QGraphicsObject *>(target)) {
+ //TODO: the first view isn't really correct - maybe use an additional field in the prepare event?
+ if (go->scene() && !go->scene()->views().isEmpty())
+ setDpiFromWidget(go->scene()->views().first());
+ }
+#endif
+
+ if (state == QScroller::Scrolling) {
+ recalcScrollingSegments();
+ }
+ return true;
+ }
+
+ return false;
+}
+
+void QScrollerPrivate::handleDrag(const QPointF &position, qint64 timestamp)
+{
+ const QScrollerPropertiesPrivate *sp = properties.d.data();
+
+ QPointF deltaPixel = position - lastPosition;
+ qint64 deltaTime = timestamp - lastTimestamp;
+
+ if (sp->axisLockThreshold) {
+ int dx = qAbs(deltaPixel.x());
+ int dy = qAbs(deltaPixel.y());
+ if (dx || dy) {
+ bool vertical = (dy > dx);
+ qreal alpha = qreal(vertical ? dx : dy) / qreal(vertical ? dy : dx);
+ //qScrollerDebug() << "QScroller::handleDrag() -- axis lock:" << alpha << " / " << axisLockThreshold << "- isvertical:" << vertical << "- dx:" << dx << "- dy:" << dy;
+ if (alpha <= sp->axisLockThreshold) {
+ if (vertical)
+ deltaPixel.setX(0);
+ else
+ deltaPixel.setY(0);
+ }
+ }
+ }
+
+ // calculate velocity (if the user would release the mouse NOW)
+ updateVelocity(deltaPixel, deltaTime);
+
+ // restrict velocity, if content is not scrollable
+ QRectF max = contentPosRange;
+ bool canScrollX = (max.width() > 0) || (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn);
+ bool canScrollY = (max.height() > 0) || (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn);
+
+ if (!canScrollX) {
+ deltaPixel.setX(0);
+ releaseVelocity.setX(0);
+ }
+ if (!canScrollY) {
+ deltaPixel.setY(0);
+ releaseVelocity.setY(0);
+ }
+
+// if (firstDrag) {
+// // Do not delay the first drag
+// setContentPositionHelper(q->contentPosition() - overshootDistance - deltaPixel);
+// dragDistance = QPointF(0, 0);
+// } else {
+ dragDistance += deltaPixel;
+// }
+//qScrollerDebug() << "######################" << deltaPixel << position.y() << lastPosition.y();
+ if (canScrollX)
+ lastPosition.setX(position.x());
+ if (canScrollY)
+ lastPosition.setY(position.y());
+ lastTimestamp = timestamp;
+}
+
+bool QScrollerPrivate::pressWhileInactive(const QPointF &position, qint64 timestamp)
+{
+ if (prepareScrolling(position)) {
+ const QScrollerPropertiesPrivate *sp = properties.d.data();
+
+ if (!contentPosRange.isNull() ||
+ (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn) ||
+ (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn)) {
+
+ lastPosition = pressPosition = position;
+ lastTimestamp = pressTimestamp = timestamp;
+ setState(QScroller::Pressed);
+ }
+ }
+ return false;
+}
+
+bool QScrollerPrivate::releaseWhilePressed(const QPointF &, qint64)
+{
+ if (overshootPosition != QPointF(0.0, 0.0)) {
+ setState(QScroller::Scrolling);
+ return true;
+ } else {
+ setState(QScroller::Inactive);
+ return false;
+ }
+}
+
+bool QScrollerPrivate::moveWhilePressed(const QPointF &position, qint64 timestamp)
+{
+ Q_Q(QScroller);
+ const QScrollerPropertiesPrivate *sp = properties.d.data();
+ QPointF ppm = q->pixelPerMeter();
+
+ QPointF deltaPixel = position - pressPosition;
+
+ bool moveAborted = false;
+ bool moveStarted = (((deltaPixel / ppm).manhattanLength()) > sp->dragStartDistance);
+
+ // check the direction of the mouse drag and abort if it's too much in the wrong direction.
+ if (moveStarted) {
+ QRectF max = contentPosRange;
+ bool canScrollX = (max.width() > 0);
+ bool canScrollY = (max.height() > 0);
+
+ if (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn)
+ canScrollX = true;
+ if (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn)
+ canScrollY = true;
+
+ if (qAbs(deltaPixel.x() / ppm.x()) < qAbs(deltaPixel.y() / ppm.y())) {
+ if (!canScrollY)
+ moveAborted = true;
+ } else {
+ if (!canScrollX)
+ moveAborted = true;
+ }
+ }
+
+ if (moveAborted) {
+ setState(QScroller::Inactive);
+ moveStarted = false;
+
+ } else if (moveStarted) {
+ setState(QScroller::Dragging);
+
+ // subtract the dragStartDistance
+ deltaPixel = deltaPixel - deltaPixel * (sp->dragStartDistance / deltaPixel.manhattanLength());
+
+ if (deltaPixel != QPointF(0, 0)) {
+ // handleDrag updates lastPosition, lastTimestamp and velocity
+ handleDrag(pressPosition + deltaPixel, timestamp);
+ }
+ }
+ return moveStarted;
+}
+
+bool QScrollerPrivate::moveWhileDragging(const QPointF &position, qint64 timestamp)
+{
+ // handleDrag updates lastPosition, lastTimestamp and velocity
+ handleDrag(position, timestamp);
+ return true;
+}
+
+void QScrollerPrivate::timerEventWhileDragging()
+{
+ if (dragDistance != QPointF(0, 0)) {
+ qScrollerDebug() << "QScroller::timerEventWhileDragging() -- dragDistance:" << dragDistance;
+
+ setContentPositionHelperDragging(-dragDistance);
+ dragDistance = QPointF(0, 0);
+ }
+}
+
+bool QScrollerPrivate::releaseWhileDragging(const QPointF &position, qint64 timestamp)
+{
+ Q_Q(QScroller);
+ const QScrollerPropertiesPrivate *sp = properties.d.data();
+
+ // handleDrag updates lastPosition, lastTimestamp and velocity
+ handleDrag(position, timestamp);
+
+ // check if we moved at all - this can happen if you stop a running
+ // scroller with a press and release shortly afterwards
+ QPointF deltaPixel = position - pressPosition;
+ if (((deltaPixel / q->pixelPerMeter()).manhattanLength()) > sp->dragStartDistance) {
+
+ // handle accelerating flicks
+ if ((oldVelocity != QPointF(0, 0)) && sp->acceleratingFlickMaximumTime &&
+ ((timestamp - pressTimestamp) < qint64(sp->acceleratingFlickMaximumTime * 1000))) {
+
+ // - determine if the direction was changed
+ int signX = 0, signY = 0;
+ if (releaseVelocity.x())
+ signX = (releaseVelocity.x() > 0) == (oldVelocity.x() > 0) ? 1 : -1;
+ if (releaseVelocity.y())
+ signY = (releaseVelocity.y() > 0) == (oldVelocity.y() > 0) ? 1 : -1;
+
+ if (signX > 0)
+ releaseVelocity.setX(qBound(-sp->maximumVelocity,
+ oldVelocity.x() * sp->acceleratingFlickSpeedupFactor,
+ sp->maximumVelocity));
+ if (signY > 0)
+ releaseVelocity.setY(qBound(-sp->maximumVelocity,
+ oldVelocity.y() * sp->acceleratingFlickSpeedupFactor,
+ sp->maximumVelocity));
+ }
+ }
+
+ QPointF ppm = q->pixelPerMeter();
+ createScrollingSegments(releaseVelocity.x(), contentPosition.x() + overshootPosition.x(), ppm.x(), Qt::Horizontal);
+ createScrollingSegments(releaseVelocity.y(), contentPosition.y() + overshootPosition.y(), ppm.y(), Qt::Vertical);
+
+ qScrollerDebug() << "QScroller::releaseWhileDragging() -- velocity:" << releaseVelocity << "-- minimum velocity:" << sp->minimumVelocity << "overshoot" << overshootPosition;
+
+ if (xSegments.isEmpty() && ySegments.isEmpty())
+ setState(QScroller::Inactive);
+ else
+ setState(QScroller::Scrolling);
+
+ return true;
+}
+
+void QScrollerPrivate::timerEventWhileScrolling()
+{
+ qScrollerDebug() << "QScroller::timerEventWhileScrolling()";
+
+ setContentPositionHelperScrolling();
+ if (xSegments.isEmpty() && ySegments.isEmpty())
+ setState(QScroller::Inactive);
+}
+
+bool QScrollerPrivate::pressWhileScrolling(const QPointF &position, qint64 timestamp)
+{
+ Q_Q(QScroller);
+
+ if ((q->velocity() <= properties.d->maximumClickThroughVelocity) &&
+ (overshootPosition == QPointF(0.0, 0.0))) {
+ setState(QScroller::Inactive);
+ return false;
+ } else {
+ lastPosition = pressPosition = position;
+ lastTimestamp = pressTimestamp = timestamp;
+ setState(QScroller::Pressed);
+ setState(QScroller::Dragging);
+ return true;
+ }
+}
+
+/*! \internal
+ This function handles all state changes of the scroller.
+*/
+void QScrollerPrivate::setState(QScroller::State newstate)
+{
+ Q_Q(QScroller);
+ bool sendLastScroll = false;
+
+ if (state == newstate)
+ return;
+
+ qScrollerDebug() << q << "QScroller::setState(" << stateName(newstate) << ")";
+
+ switch (newstate) {
+ case QScroller::Inactive:
+#ifndef QT_NO_ANIMATION
+ scrollTimer->stop();
+#endif
+
+ // send the last scroll event (but only after the current state change was finished)
+ if (!firstScroll)
+ sendLastScroll = true;
+
+ releaseVelocity = QPointF(0, 0);
+ break;
+
+ case QScroller::Pressed:
+#ifndef QT_NO_ANIMATION
+ scrollTimer->stop();
+#endif
+
+ oldVelocity = releaseVelocity;
+ releaseVelocity = QPointF(0, 0);
+ break;
+
+ case QScroller::Dragging:
+ dragDistance = QPointF(0, 0);
+#ifndef QT_NO_ANIMATION
+ if (state == QScroller::Pressed)
+ scrollTimer->start();
+#endif
+ break;
+
+ case QScroller::Scrolling:
+#ifndef QT_NO_ANIMATION
+ scrollTimer->start();
+#endif
+ break;
+ }
+
+ qSwap(state, newstate);
+
+ if (sendLastScroll) {
+ QScrollEvent se(contentPosition, overshootPosition, QScrollEvent::ScrollFinished);
+ sendEvent(target, &se);
+ firstScroll = true;
+ }
+ if (state == QScroller::Dragging || state == QScroller::Scrolling)
+ qt_activeScrollers()->insert(q);
+ else
+ qt_activeScrollers()->remove(q);
+ emit q->stateChanged(state);
+}
+
+
+/*! \internal
+ Helps when setting the content position.
+ It will try to move the content by the requested delta but stop in case
+ when we are coming back from an overshoot or a scrollTo.
+ It will also indicate a new overshooting condition by the overshootX and oversthootY flags.
+
+ In this cases it will reset the velocity variables and other flags.
+
+ Also keeps track of the current over-shooting value in overshootPosition.
+
+ \a deltaPos is the amount of pixels the current content position should be moved
+*/
+void QScrollerPrivate::setContentPositionHelperDragging(const QPointF &deltaPos)
+{
+ const QScrollerPropertiesPrivate *sp = properties.d.data();
+
+ if (sp->overshootDragResistanceFactor)
+ overshootPosition /= sp->overshootDragResistanceFactor;
+
+ QPointF oldPos = contentPosition + overshootPosition;
+ QPointF newPos = oldPos + deltaPos;
+
+ qScrollerDebug() << "QScroller::setContentPositionHelperDragging(" << deltaPos << " [pix])";
+ qScrollerDebug() << " --> overshoot:" << overshootPosition << "- old pos:" << oldPos << "- new pos:" << newPos;
+
+ QPointF oldClampedPos = clampToRect(oldPos, contentPosRange);
+ QPointF newClampedPos = clampToRect(newPos, contentPosRange);
+
+ // --- handle overshooting and stop if the coordinate is going back inside the normal area
+ bool alwaysOvershootX = (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn);
+ bool alwaysOvershootY = (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn);
+ bool noOvershootX = (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOff) ||
+ ((state == QScroller::Dragging) && !sp->overshootDragResistanceFactor) ||
+ !sp->overshootDragDistanceFactor;
+ bool noOvershootY = (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOff) ||
+ ((state == QScroller::Dragging) && !sp->overshootDragResistanceFactor) ||
+ !sp->overshootDragDistanceFactor;
+ bool canOvershootX = !noOvershootX && (alwaysOvershootX || contentPosRange.width());
+ bool canOvershootY = !noOvershootY && (alwaysOvershootY || contentPosRange.height());
+
+ qreal oldOvershootX = (canOvershootX) ? oldPos.x() - oldClampedPos.x() : 0;
+ qreal oldOvershootY = (canOvershootY) ? oldPos.y() - oldClampedPos.y() : 0;
+
+ qreal newOvershootX = (canOvershootX) ? newPos.x() - newClampedPos.x() : 0;
+ qreal newOvershootY = (canOvershootY) ? newPos.y() - newClampedPos.y() : 0;
+
+ qreal maxOvershootX = viewportSize.width() * sp->overshootDragDistanceFactor;
+ qreal maxOvershootY = viewportSize.height() * sp->overshootDragDistanceFactor;
+
+ qScrollerDebug() << " --> noOs:" << noOvershootX << "drf:" << sp->overshootDragResistanceFactor << "mdf:" << sp->overshootScrollDistanceFactor << "ossP:"<<sp->hOvershootPolicy;
+ qScrollerDebug() << " --> canOS:" << canOvershootX << "newOS:" << newOvershootX << "maxOS:" << maxOvershootX;
+
+ if (sp->overshootDragResistanceFactor) {
+ oldOvershootX *= sp->overshootDragResistanceFactor;
+ oldOvershootY *= sp->overshootDragResistanceFactor;
+ newOvershootX *= sp->overshootDragResistanceFactor;
+ newOvershootY *= sp->overshootDragResistanceFactor;
+ }
+
+ // -- stop at the maximum overshoot distance
+
+ newOvershootX = qBound(-maxOvershootX, newOvershootX, maxOvershootX);
+ newOvershootY = qBound(-maxOvershootY, newOvershootY, maxOvershootY);
+
+ overshootPosition.setX(newOvershootX);
+ overshootPosition.setY(newOvershootY);
+ contentPosition = newClampedPos;
+
+ QScrollEvent se(contentPosition, overshootPosition, firstScroll ? QScrollEvent::ScrollStarted : QScrollEvent::ScrollUpdated);
+ sendEvent(target, &se);
+ firstScroll = false;
+
+ qScrollerDebug() << " --> new position:" << newClampedPos << "- new overshoot:" << overshootPosition <<
+ "- overshoot x/y?:" << overshootPosition;
+}
+
+
+qreal QScrollerPrivate::nextSegmentPosition(QQueue<ScrollSegment> &segments, qint64 now, qreal oldPos)
+{
+ qreal pos = oldPos;
+
+ // check the X segments for new positions
+ while (!segments.isEmpty()) {
+ const ScrollSegment s = segments.head();
+
+ if ((s.startTime + s.deltaTime * s.stopProgress) <= now) {
+ segments.dequeue();
+ pos = s.stopPos;
+ } else if (s.startTime <= now) {
+ qreal progress = qreal(now - s.startTime) / qreal(s.deltaTime);
+ pos = s.startPos + s.deltaPos * s.curve.valueForProgress(progress);
+ if (s.deltaPos > 0 ? pos > s.stopPos : pos < s.stopPos) {
+ segments.dequeue();
+ pos = s.stopPos;
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ return pos;
+}
+
+void QScrollerPrivate::setContentPositionHelperScrolling()
+{
+ qint64 now = monotonicTimer.elapsed();
+ QPointF newPos = contentPosition + overshootPosition;
+
+ newPos.setX(nextSegmentPosition(xSegments, now, newPos.x()));
+ newPos.setY(nextSegmentPosition(ySegments, now, newPos.y()));
+
+ // -- set the position and handle overshoot
+ qScrollerDebug() << "QScroller::setContentPositionHelperScrolling()";
+ qScrollerDebug() << " --> overshoot:" << overshootPosition << "- new pos:" << newPos;
+
+ QPointF newClampedPos = clampToRect(newPos, contentPosRange);
+
+ overshootPosition = newPos - newClampedPos;
+ contentPosition = newClampedPos;
+
+ QScrollEvent se(contentPosition, overshootPosition, firstScroll ? QScrollEvent::ScrollStarted : QScrollEvent::ScrollUpdated);
+ sendEvent(target, &se);
+ firstScroll = false;
+
+ qScrollerDebug() << " --> new position:" << newClampedPos << "- new overshoot:" << overshootPosition;
+}
+
+/*! \internal
+ Returns the next snap point in direction.
+ If \a direction >0 it will return the next snap point that is larger than the current position.
+ If \a direction <0 it will return the next snap point that is smaller than the current position.
+ If \a direction ==0 it will return the nearest snap point (or the current position if we are already
+ on a snap point.
+ Returns the nearest snap position or NaN if no such point could be found.
+ */
+qreal QScrollerPrivate::nextSnapPos(qreal p, int dir, Qt::Orientation orientation)
+{
+ qreal bestSnapPos = Q_QNAN;
+ qreal bestSnapPosDist = Q_INFINITY;
+
+ qreal minPos;
+ qreal maxPos;
+
+ if (orientation == Qt::Horizontal) {
+ minPos = contentPosRange.left();
+ maxPos = contentPosRange.right();
+ } else {
+ minPos = contentPosRange.top();
+ maxPos = contentPosRange.bottom();
+ }
+
+ if (orientation == Qt::Horizontal) {
+ // the snap points in the list
+ foreach (qreal snapPos, snapPositionsX) {
+ qreal snapPosDist = snapPos - p;
+ if ((dir > 0 && snapPosDist < 0) ||
+ (dir < 0 && snapPosDist > 0))
+ continue; // wrong direction
+ if (snapPos < minPos || snapPos > maxPos )
+ continue; // invalid
+
+ if (qIsNaN(bestSnapPos) ||
+ qAbs(snapPosDist) < bestSnapPosDist ) {
+ bestSnapPos = snapPos;
+ bestSnapPosDist = qAbs(snapPosDist);
+ }
+ }
+
+ // the snap point interval
+ if (snapIntervalX > 0.0) {
+ qreal first = minPos + snapFirstX;
+ qreal snapPos;
+ if (dir > 0)
+ snapPos = qCeil((p - first) / snapIntervalX) * snapIntervalX + first;
+ else if (dir < 0)
+ snapPos = qFloor((p - first) / snapIntervalX) * snapIntervalX + first;
+ else if (p <= first)
+ snapPos = first;
+ else
+ {
+ qreal last = qFloor((maxPos - first) / snapIntervalX) * snapIntervalX + first;
+ if (p >= last)
+ snapPos = last;
+ else
+ snapPos = qRound((p - first) / snapIntervalX) * snapIntervalX + first;
+ }
+
+ if (snapPos >= first && snapPos <= maxPos ) {
+ qreal snapPosDist = snapPos - p;
+
+ if (qIsNaN(bestSnapPos) ||
+ qAbs(snapPosDist) < bestSnapPosDist ) {
+ bestSnapPos = snapPos;
+ bestSnapPosDist = qAbs(snapPosDist);
+ }
+ }
+ }
+
+ } else { // (orientation == Qt::Vertical)
+ // the snap points in the list
+ foreach (qreal snapPos, snapPositionsY) {
+ qreal snapPosDist = snapPos - p;
+ if ((dir > 0 && snapPosDist < 0) ||
+ (dir < 0 && snapPosDist > 0))
+ continue; // wrong direction
+ if (snapPos < minPos || snapPos > maxPos )
+ continue; // invalid
+
+ if (qIsNaN(bestSnapPos) ||
+ qAbs(snapPosDist) < bestSnapPosDist) {
+ bestSnapPos = snapPos;
+ bestSnapPosDist = qAbs(snapPosDist);
+ }
+ }
+
+ // the snap point interval
+ if (snapIntervalY > 0.0) {
+ qreal first = minPos + snapFirstY;
+ qreal snapPos;
+ if (dir > 0)
+ snapPos = qCeil((p - first) / snapIntervalY) * snapIntervalY + first;
+ else if (dir < 0)
+ snapPos = qFloor((p - first) / snapIntervalY) * snapIntervalY + first;
+ else if (p <= first)
+ snapPos = first;
+ else
+ {
+ qreal last = qFloor((maxPos - first) / snapIntervalY) * snapIntervalY + first;
+ if (p >= last)
+ snapPos = last;
+ else
+ snapPos = qRound((p - first) / snapIntervalY) * snapIntervalY + first;
+ }
+
+ if (snapPos >= first && snapPos <= maxPos ) {
+ qreal snapPosDist = snapPos - p;
+
+ if (qIsNaN(bestSnapPos) ||
+ qAbs(snapPosDist) < bestSnapPosDist) {
+ bestSnapPos = snapPos;
+ bestSnapPosDist = qAbs(snapPosDist);
+ }
+ }
+ }
+ }
+
+ return bestSnapPos;
+}
+
+/*!
+ \enum QScroller::State
+
+ This enum contains the different QScroller states.
+
+ \value Inactive The scroller is not scrolling and nothing is pressed.
+ \value Pressed A touch event was received or the mouse button was pressed but the scroll area is currently not dragged.
+ \value Dragging The scroll area is currently following the touch point or mouse.
+ \value Scrolling The scroll area is moving on it's own.
+*/
+
+/*!
+ \enum QScroller::ScrollerGestureType
+
+ This enum contains the different gesture types that are supported by the QScroller gesture recognizer.
+
+ \value TouchGesture The gesture recognizer will only trigger on touch
+ events. Specifically it will react on single touch points when using a
+ touch screen and dual touch points when using a touchpad.
+ \value LeftMouseButtonGesture The gesture recognizer will only trigger on left mouse button events.
+ \value MiddleMouseButtonGesture The gesture recognizer will only trigger on middle mouse button events.
+ \value RightMouseButtonGesture The gesture recognizer will only trigger on right mouse button events.
+*/
+
+/*!
+ \enum QScroller::Input
+
+ This enum contains an input device agnostic view of input events that are relevant for QScroller.
+
+ \value InputPress The user pressed the input device (e.g. QEvent::MouseButtonPress,
+ QEvent::GraphicsSceneMousePress, QEvent::TouchBegin)
+
+ \value InputMove The user moved the input device (e.g. QEvent::MouseMove,
+ QEvent::GraphicsSceneMouseMove, QEvent::TouchUpdate)
+
+ \value InputRelease The user released the input device (e.g. QEvent::MouseButtonRelease,
+ QEvent::GraphicsSceneMouseRelease, QEvent::TouchEnd)
+
+*/
+
+QT_END_NAMESPACE
diff --git a/src/widgets/util/qscroller.h b/src/widgets/util/qscroller.h
new file mode 100644
index 0000000000..3484139cbb
--- /dev/null
+++ b/src/widgets/util/qscroller.h
@@ -0,0 +1,155 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSCROLLER_H
+#define QSCROLLER_H
+
+#include <QtCore/QObject>
+#include <QtCore/QPointF>
+#include <QtWidgets/QScrollerProperties>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Gui)
+
+class QWidget;
+class QScrollerPrivate;
+class QScrollerProperties;
+#ifndef QT_NO_GESTURES
+class QFlickGestureRecognizer;
+class QMouseFlickGestureRecognizer;
+#endif
+
+class Q_WIDGETS_EXPORT QScroller : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(State state READ state NOTIFY stateChanged)
+ Q_PROPERTY(QScrollerProperties scrollerProperties READ scrollerProperties WRITE setScrollerProperties NOTIFY scrollerPropertiesChanged)
+ Q_ENUMS(State)
+
+public:
+ enum State
+ {
+ Inactive,
+ Pressed,
+ Dragging,
+ Scrolling
+ };
+
+ enum ScrollerGestureType
+ {
+ TouchGesture,
+ LeftMouseButtonGesture,
+ RightMouseButtonGesture,
+ MiddleMouseButtonGesture
+ };
+
+ enum Input
+ {
+ InputPress = 1,
+ InputMove,
+ InputRelease
+ };
+
+ static bool hasScroller(QObject *target);
+
+ static QScroller *scroller(QObject *target);
+ static const QScroller *scroller(const QObject *target);
+
+#ifndef QT_NO_GESTURES
+ static Qt::GestureType grabGesture(QObject *target, ScrollerGestureType gestureType = TouchGesture);
+ static Qt::GestureType grabbedGesture(QObject *target);
+ static void ungrabGesture(QObject *target);
+#endif
+
+ static QList<QScroller *> activeScrollers();
+
+ QObject *target() const;
+
+ State state() const;
+
+ bool handleInput(Input input, const QPointF &position, qint64 timestamp = 0);
+
+ void stop();
+ QPointF velocity() const;
+ QPointF finalPosition() const;
+ QPointF pixelPerMeter() const;
+
+ QScrollerProperties scrollerProperties() const;
+
+ void setSnapPositionsX( const QList<qreal> &positions );
+ void setSnapPositionsX( qreal first, qreal interval );
+ void setSnapPositionsY( const QList<qreal> &positions );
+ void setSnapPositionsY( qreal first, qreal interval );
+
+public Q_SLOTS:
+ void setScrollerProperties(const QScrollerProperties &prop);
+ void scrollTo(const QPointF &pos);
+ void scrollTo(const QPointF &pos, int scrollTime);
+ void ensureVisible(const QRectF &rect, qreal xmargin, qreal ymargin);
+ void ensureVisible(const QRectF &rect, qreal xmargin, qreal ymargin, int scrollTime);
+ void resendPrepareEvent();
+
+Q_SIGNALS:
+ void stateChanged(QScroller::State newstate);
+ void scrollerPropertiesChanged(const QScrollerProperties &);
+
+private:
+ QScrollerPrivate *d_ptr;
+
+ QScroller(QObject *target);
+ virtual ~QScroller();
+
+ Q_DISABLE_COPY(QScroller)
+ Q_DECLARE_PRIVATE(QScroller)
+
+#ifndef QT_NO_GESTURES
+ friend class QFlickGestureRecognizer;
+#endif
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QSCROLLER_H
diff --git a/src/widgets/util/qscroller_mac.mm b/src/widgets/util/qscroller_mac.mm
new file mode 100644
index 0000000000..81d18aaf6b
--- /dev/null
+++ b/src/widgets/util/qscroller_mac.mm
@@ -0,0 +1,75 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore/qglobal.h>
+
+#ifdef Q_WS_MAC
+
+#import <Cocoa/Cocoa.h>
+
+#include "qscroller_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QPointF QScrollerPrivate::realDpi(int screen)
+{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ NSArray *nsscreens = [NSScreen screens];
+
+ if (screen < 0 || screen >= int([nsscreens count]))
+ screen = 0;
+
+ NSScreen *nsscreen = [nsscreens objectAtIndex:screen];
+ CGDirectDisplayID display = [[[nsscreen deviceDescription] objectForKey:@"NSScreenNumber"] intValue];
+
+ CGSize mmsize = CGDisplayScreenSize(display);
+ if (mmsize.width > 0 && mmsize.height > 0) {
+ return QPointF(CGDisplayPixelsWide(display) / mmsize.width,
+ CGDisplayPixelsHigh(display) / mmsize.height) * qreal(25.4);
+ } else {
+ return QPointF();
+ }
+ [pool release];
+}
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/widgets/util/qscroller_p.h b/src/widgets/util/qscroller_p.h
new file mode 100644
index 0000000000..b4f4db5e34
--- /dev/null
+++ b/src/widgets/util/qscroller_p.h
@@ -0,0 +1,209 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSCROLLER_P_H
+#define QSCROLLER_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 <QObject>
+#include <QPointer>
+#include <QQueue>
+#include <QSet>
+#include <QEasingCurve>
+#include <QElapsedTimer>
+#include <QSizeF>
+#include <QPointF>
+#include <QRectF>
+#include <qscroller.h>
+#include <qscrollerproperties.h>
+#include <private/qscrollerproperties_p.h>
+#include <QAbstractAnimation>
+
+QT_BEGIN_NAMESPACE
+
+#ifndef QT_NO_GESTURES
+class QFlickGestureRecognizer;
+#endif
+
+#ifndef QT_NO_ANIMATION
+class QScrollTimer;
+#endif
+class QScrollerPrivate : public QObject
+{
+ Q_OBJECT
+ Q_DECLARE_PUBLIC(QScroller)
+
+public:
+ QScrollerPrivate(QScroller *q, QObject *target);
+ void init();
+
+ void sendEvent(QObject *o, QEvent *e);
+
+ void setState(QScroller::State s);
+
+ enum ScrollType {
+ ScrollTypeFlick = 0,
+ ScrollTypeScrollTo,
+ ScrollTypeOvershoot
+ };
+
+ struct ScrollSegment {
+ qint64 startTime;
+ qint64 deltaTime;
+ qreal startPos;
+ qreal deltaPos;
+ QEasingCurve curve;
+ qreal stopProgress; // whatever is..
+ qreal stopPos; // ..reached first
+ ScrollType type;
+ };
+
+ bool pressWhileInactive(const QPointF &position, qint64 timestamp);
+ bool moveWhilePressed(const QPointF &position, qint64 timestamp);
+ bool releaseWhilePressed(const QPointF &position, qint64 timestamp);
+ bool moveWhileDragging(const QPointF &position, qint64 timestamp);
+ bool releaseWhileDragging(const QPointF &position, qint64 timestamp);
+ bool pressWhileScrolling(const QPointF &position, qint64 timestamp);
+
+ void timerTick();
+ void timerEventWhileDragging();
+ void timerEventWhileScrolling();
+
+ bool prepareScrolling(const QPointF &position);
+ void handleDrag(const QPointF &position, qint64 timestamp);
+
+ QPointF realDpi(int screen);
+ QPointF dpi() const;
+ void setDpi(const QPointF &dpi);
+ void setDpiFromWidget(QWidget *widget);
+
+ void updateVelocity(const QPointF &deltaPixelRaw, qint64 deltaTime);
+ void pushSegment(ScrollType type, qreal deltaTime, qreal stopProgress, qreal startPos, qreal deltaPos, qreal stopPos, QEasingCurve::Type curve, Qt::Orientation orientation);
+ void recalcScrollingSegments(bool forceRecalc = false);
+ qreal scrollingSegmentsEndPos(Qt::Orientation orientation) const;
+ bool scrollingSegmentsValid(Qt::Orientation orientation);
+ void createScrollToSegments(qreal v, qreal deltaTime, qreal endPos, Qt::Orientation orientation, ScrollType type);
+ void createScrollingSegments(qreal v, qreal startPos, qreal ppm, Qt::Orientation orientation);
+
+ void setContentPositionHelperDragging(const QPointF &deltaPos);
+ void setContentPositionHelperScrolling();
+
+ qreal nextSnapPos(qreal p, int dir, Qt::Orientation orientation);
+ static qreal nextSegmentPosition(QQueue<ScrollSegment> &segments, qint64 now, qreal oldPos);
+
+ inline int frameRateSkip() const { return properties.d.data()->frameRate; }
+
+ static const char *stateName(QScroller::State state);
+ static const char *inputName(QScroller::Input input);
+
+public slots:
+ void targetDestroyed();
+
+public:
+ // non static
+ QObject *target;
+ QScrollerProperties properties;
+#ifndef QT_NO_GESTURES
+ QFlickGestureRecognizer *recognizer;
+ Qt::GestureType recognizerType;
+#endif
+
+ // scroller state:
+
+ // QPointer<QObject> scrollTarget;
+ QSizeF viewportSize;
+ QRectF contentPosRange;
+ QPointF contentPosition;
+ QPointF overshootPosition; // the number of pixels we are overshooting (before overshootDragResistanceFactor)
+
+ // state
+
+ bool enabled;
+ QScroller::State state;
+ bool firstScroll; // true if we haven't already send a scroll event
+
+ QPointF oldVelocity; // the release velocity of the last drag
+
+ QPointF pressPosition;
+ QPointF lastPosition;
+ qint64 pressTimestamp;
+ qint64 lastTimestamp;
+
+ QPointF dragDistance; // the distance we should move during the next drag timer event
+
+ QQueue<ScrollSegment> xSegments;
+ QQueue<ScrollSegment> ySegments;
+
+ // snap positions
+ QList<qreal> snapPositionsX;
+ qreal snapFirstX;
+ qreal snapIntervalX;
+ QList<qreal> snapPositionsY;
+ qreal snapFirstY;
+ qreal snapIntervalY;
+
+ QPointF pixelPerMeter;
+
+ QElapsedTimer monotonicTimer;
+
+ QPointF releaseVelocity; // the starting velocity of the scrolling state
+#ifndef QT_NO_ANIMATION
+ QScrollTimer *scrollTimer;
+#endif
+
+ QScroller *q_ptr;
+};
+
+
+QT_END_NAMESPACE
+
+#endif // QSCROLLER_P_H
+
diff --git a/src/widgets/util/qscrollerproperties.cpp b/src/widgets/util/qscrollerproperties.cpp
new file mode 100644
index 0000000000..5e2459a37e
--- /dev/null
+++ b/src/widgets/util/qscrollerproperties.cpp
@@ -0,0 +1,393 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QPointer>
+#include <QObject>
+#include <QtCore/qmath.h>
+#ifdef Q_WS_WIN
+# include <QLibrary>
+#endif
+
+#include "qscrollerproperties.h"
+#include "private/qscrollerproperties_p.h"
+
+QT_BEGIN_NAMESPACE
+
+static QScrollerPropertiesPrivate *userDefaults = 0;
+static QScrollerPropertiesPrivate *systemDefaults = 0;
+
+QScrollerPropertiesPrivate *QScrollerPropertiesPrivate::defaults()
+{
+ if (!systemDefaults) {
+ QScrollerPropertiesPrivate spp;
+ spp.mousePressEventDelay = qreal(0.25);
+ spp.dragStartDistance = qreal(5.0 / 1000);
+ spp.dragVelocitySmoothingFactor = qreal(0.8);
+ spp.axisLockThreshold = qreal(0);
+ spp.scrollingCurve.setType(QEasingCurve::OutQuad);
+ spp.decelerationFactor = qreal(0.125);
+ spp.minimumVelocity = qreal(50.0 / 1000);
+ spp.maximumVelocity = qreal(500.0 / 1000);
+ spp.maximumClickThroughVelocity = qreal(66.5 / 1000);
+ spp.acceleratingFlickMaximumTime = qreal(1.25);
+ spp.acceleratingFlickSpeedupFactor = qreal(3.0);
+ spp.snapPositionRatio = qreal(0.5);
+ spp.snapTime = qreal(0.3);
+ spp.overshootDragResistanceFactor = qreal(0.5);
+ spp.overshootDragDistanceFactor = qreal(1);
+ spp.overshootScrollDistanceFactor = qreal(0.5);
+ spp.overshootScrollTime = qreal(0.7);
+# ifdef Q_WS_WIN
+ if (QLibrary::resolve(QLatin1String("UxTheme"), "BeginPanningFeedback"))
+ spp.overshootScrollTime = qreal(0.35);
+# endif
+ spp.hOvershootPolicy = QScrollerProperties::OvershootWhenScrollable;
+ spp.vOvershootPolicy = QScrollerProperties::OvershootWhenScrollable;
+ spp.frameRate = QScrollerProperties::Standard;
+
+ systemDefaults = new QScrollerPropertiesPrivate(spp);
+ }
+ return new QScrollerPropertiesPrivate(userDefaults ? *userDefaults : *systemDefaults);
+}
+
+/*!
+ \class QScrollerProperties
+ \brief The QScrollerProperties class stores the settings for a QScroller.
+ \since 4.8
+
+ The QScrollerProperties class stores the parameters used by QScroller.
+
+ The default settings are platform dependent so that Qt emulates the
+ platform behaviour for kinetic scrolling.
+
+ As a convention the QScrollerProperties are in physical units (meter,
+ seconds) and are converted by QScroller using the current DPI.
+
+ \sa QScroller
+*/
+
+/*!
+ Constructs new scroller properties.
+*/
+QScrollerProperties::QScrollerProperties()
+ : d(QScrollerPropertiesPrivate::defaults())
+{
+}
+
+/*!
+ Constructs a copy of \a sp.
+*/
+QScrollerProperties::QScrollerProperties(const QScrollerProperties &sp)
+ : d(new QScrollerPropertiesPrivate(*sp.d))
+{
+}
+
+/*!
+ Assigns \a sp to these scroller properties and returns a reference to these scroller properties.
+*/
+QScrollerProperties &QScrollerProperties::operator=(const QScrollerProperties &sp)
+{
+ *d.data() = *sp.d.data();
+ return *this;
+}
+
+/*!
+ Destroys the scroller properties.
+*/
+QScrollerProperties::~QScrollerProperties()
+{
+}
+
+/*!
+ Returns true if these scroller properties are equal to \a sp; otherwise returns false.
+*/
+bool QScrollerProperties::operator==(const QScrollerProperties &sp) const
+{
+ return *d.data() == *sp.d.data();
+}
+
+/*!
+ Returns true if these scroller properties are different from \a sp; otherwise returns false.
+*/
+bool QScrollerProperties::operator!=(const QScrollerProperties &sp) const
+{
+ return !(*d.data() == *sp.d.data());
+}
+
+bool QScrollerPropertiesPrivate::operator==(const QScrollerPropertiesPrivate &p) const
+{
+ bool same = true;
+ same &= (mousePressEventDelay == p.mousePressEventDelay);
+ same &= (dragStartDistance == p.dragStartDistance);
+ same &= (dragVelocitySmoothingFactor == p.dragVelocitySmoothingFactor);
+ same &= (axisLockThreshold == p.axisLockThreshold);
+ same &= (scrollingCurve == p.scrollingCurve);
+ same &= (decelerationFactor == p.decelerationFactor);
+ same &= (minimumVelocity == p.minimumVelocity);
+ same &= (maximumVelocity == p.maximumVelocity);
+ same &= (maximumClickThroughVelocity == p.maximumClickThroughVelocity);
+ same &= (acceleratingFlickMaximumTime == p.acceleratingFlickMaximumTime);
+ same &= (acceleratingFlickSpeedupFactor == p.acceleratingFlickSpeedupFactor);
+ same &= (snapPositionRatio == p.snapPositionRatio);
+ same &= (snapTime == p.snapTime);
+ same &= (overshootDragResistanceFactor == p.overshootDragResistanceFactor);
+ same &= (overshootDragDistanceFactor == p.overshootDragDistanceFactor);
+ same &= (overshootScrollDistanceFactor == p.overshootScrollDistanceFactor);
+ same &= (overshootScrollTime == p.overshootScrollTime);
+ same &= (hOvershootPolicy == p.hOvershootPolicy);
+ same &= (vOvershootPolicy == p.vOvershootPolicy);
+ same &= (frameRate == p.frameRate);
+ return same;
+}
+
+/*!
+ Sets the scroller properties for all new QScrollerProperties objects to \a sp.
+
+ Use this function to override the platform default properties returned by the default
+ constructor. If you only want to change the scroller properties of a single scroller, use
+ QScroller::setScrollerProperties()
+
+ \note Calling this function will not change the content of already existing
+ QScrollerProperties objects.
+
+ \sa unsetDefaultScrollerProperties()
+*/
+void QScrollerProperties::setDefaultScrollerProperties(const QScrollerProperties &sp)
+{
+ if (!userDefaults)
+ userDefaults = new QScrollerPropertiesPrivate(*sp.d);
+ else
+ *userDefaults = *sp.d;
+}
+
+/*!
+ Sets the scroller properties returned by the default constructor back to the platform default
+ properties.
+
+ \sa setDefaultScrollerProperties()
+*/
+void QScrollerProperties::unsetDefaultScrollerProperties()
+{
+ delete userDefaults;
+ userDefaults = 0;
+}
+
+/*!
+ Query the \a metric value of the scroller properties.
+
+ \sa setScrollMetric(), ScrollMetric
+*/
+QVariant QScrollerProperties::scrollMetric(ScrollMetric metric) const
+{
+ switch (metric) {
+ case MousePressEventDelay: return d->mousePressEventDelay;
+ case DragStartDistance: return d->dragStartDistance;
+ case DragVelocitySmoothingFactor: return d->dragVelocitySmoothingFactor;
+ case AxisLockThreshold: return d->axisLockThreshold;
+ case ScrollingCurve: return d->scrollingCurve;
+ case DecelerationFactor: return d->decelerationFactor;
+ case MinimumVelocity: return d->minimumVelocity;
+ case MaximumVelocity: return d->maximumVelocity;
+ case MaximumClickThroughVelocity: return d->maximumClickThroughVelocity;
+ case AcceleratingFlickMaximumTime: return d->acceleratingFlickMaximumTime;
+ case AcceleratingFlickSpeedupFactor:return d->acceleratingFlickSpeedupFactor;
+ case SnapPositionRatio: return d->snapPositionRatio;
+ case SnapTime: return d->snapTime;
+ case OvershootDragResistanceFactor: return d->overshootDragResistanceFactor;
+ case OvershootDragDistanceFactor: return d->overshootDragDistanceFactor;
+ case OvershootScrollDistanceFactor: return d->overshootScrollDistanceFactor;
+ case OvershootScrollTime: return d->overshootScrollTime;
+ case HorizontalOvershootPolicy: return QVariant::fromValue(d->hOvershootPolicy);
+ case VerticalOvershootPolicy: return QVariant::fromValue(d->vOvershootPolicy);
+ case FrameRate: return QVariant::fromValue(d->frameRate);
+ case ScrollMetricCount: break;
+ }
+ return QVariant();
+}
+
+/*!
+ Set a specific value of the \a metric ScrollerMetric to \a value.
+
+ \sa scrollMetric(), ScrollMetric
+*/
+void QScrollerProperties::setScrollMetric(ScrollMetric metric, const QVariant &value)
+{
+ switch (metric) {
+ case MousePressEventDelay: d->mousePressEventDelay = value.toReal(); break;
+ case DragStartDistance: d->dragStartDistance = value.toReal(); break;
+ case DragVelocitySmoothingFactor: d->dragVelocitySmoothingFactor = qBound(qreal(0), value.toReal(), qreal(1)); break;
+ case AxisLockThreshold: d->axisLockThreshold = qBound(qreal(0), value.toReal(), qreal(1)); break;
+ case ScrollingCurve: d->scrollingCurve = value.toEasingCurve(); break;
+ case DecelerationFactor: d->decelerationFactor = value.toReal(); break;
+ case MinimumVelocity: d->minimumVelocity = value.toReal(); break;
+ case MaximumVelocity: d->maximumVelocity = value.toReal(); break;
+ case MaximumClickThroughVelocity: d->maximumClickThroughVelocity = value.toReal(); break;
+ case AcceleratingFlickMaximumTime: d->acceleratingFlickMaximumTime = value.toReal(); break;
+ case AcceleratingFlickSpeedupFactor:d->acceleratingFlickSpeedupFactor = value.toReal(); break;
+ case SnapPositionRatio: d->snapPositionRatio = qBound(qreal(0), value.toReal(), qreal(1)); break;
+ case SnapTime: d->snapTime = value.toReal(); break;
+ case OvershootDragResistanceFactor: d->overshootDragResistanceFactor = value.toReal(); break;
+ case OvershootDragDistanceFactor: d->overshootDragDistanceFactor = qBound(qreal(0), value.toReal(), qreal(1)); break;
+ case OvershootScrollDistanceFactor: d->overshootScrollDistanceFactor = qBound(qreal(0), value.toReal(), qreal(1)); break;
+ case OvershootScrollTime: d->overshootScrollTime = value.toReal(); break;
+ case HorizontalOvershootPolicy: d->hOvershootPolicy = value.value<QScrollerProperties::OvershootPolicy>(); break;
+ case VerticalOvershootPolicy: d->vOvershootPolicy = value.value<QScrollerProperties::OvershootPolicy>(); break;
+ case FrameRate: d->frameRate = value.value<QScrollerProperties::FrameRates>(); break;
+ case ScrollMetricCount: break;
+ }
+}
+
+/*!
+ \enum QScrollerProperties::FrameRates
+
+ This enum describes the available frame rates used while dragging or scrolling.
+
+ \value Fps60 60 frames per second
+ \value Fps30 30 frames per second
+ \value Fps20 20 frames per second
+ \value Standard the default value is 60 frames per second (which corresponds to QAbstractAnimation).
+*/
+
+/*!
+ \enum QScrollerProperties::OvershootPolicy
+
+ This enum describes the various modes of overshooting.
+
+ \value OvershootWhenScrollable Overshooting is possible when the content is scrollable. This is the
+ default.
+
+ \value OvershootAlwaysOff Overshooting is never enabled, even when the content is scrollable.
+
+ \value OvershootAlwaysOn Overshooting is always enabled, even when the content is not
+ scrollable.
+*/
+
+/*!
+ \enum QScrollerProperties::ScrollMetric
+
+ This enum contains the different scroll metric types. When not indicated otherwise the
+ setScrollMetric function expects a QVariant of type qreal.
+
+ See the QScroller documentation for further details of the concepts behind the different
+ values.
+
+ \value MousePressEventDelay This is the time a mouse press event is delayed when starting
+ a flick gesture in \c{[s]}. If the gesture is triggered within that time, no mouse press or
+ release is sent to the scrolled object. If it triggers after that delay the delayed
+ mouse press plus a faked release event at global postion \c{QPoint(-QWIDGETSIZE_MAX,
+ -QWIDGETSIZE_MAX)} is sent. If the gesture is canceled, then both the delayed mouse
+ press plus the real release event are delivered.
+
+ \value DragStartDistance This is the minimum distance the touch or mouse point needs to be
+ moved before the flick gesture is triggered in \c m.
+
+ \value DragVelocitySmoothingFactor A value that describes to which extent new drag velocities are
+ included in the final scrolling velocity. This value should be in the range between \c 0 and
+ \c 1. The lower the value, the more smoothing is applied to the dragging velocity.
+
+ \value AxisLockThreshold Restricts the movement to one axis if the movement is inside an angle
+ around the axis. The threshold must be in the range \c 0 to \c 1.
+
+ \value ScrollingCurve The QEasingCurve used when decelerating the scrolling velocity after an
+ user initiated flick. Please note that this is the easing curve for the positions, \bold{not}
+ the velocity: the default is QEasingCurve::OutQuad, which results in a linear decrease in
+ velocity (1st derivative) and a constant deceleration (2nd derivative).
+
+ \value DecelerationFactor This factor influences how long it takes the scroller to decelerate
+ to 0 velocity. The actual value depends on the chosen ScrollingCurve. For most
+ types the value should be in the range from \c 0.1 to \c 2.0
+
+ \value MinimumVelocity The minimum velocity that is needed after ending the touch or releasing
+ the mouse to start scrolling in \c{m/s}.
+
+ \value MaximumVelocity This is the maximum velocity that can be reached in \c{m/s}.
+
+ \value MaximumClickThroughVelocity This is the maximum allowed scroll speed for a click-through
+ in \c{m/s}. This means that a click on a currently (slowly) scrolling object will not only stop
+ the scrolling but the click event will also be delivered to the UI control. This is
+ useful when using exponential-type scrolling curves.
+
+ \value AcceleratingFlickMaximumTime This is the maximum time in \c seconds that a flick gesture
+ can take to be recognized as an accelerating flick. If set to zero no such gesture is
+ detected. An "accelerating flick" is a flick gesture executed on an already scrolling object.
+ In such cases the scrolling speed is multiplied by AcceleratingFlickSpeedupFactor in order to
+ accelerate it.
+
+ \value AcceleratingFlickSpeedupFactor The current speed is multiplied by this number if an
+ accelerating flick is detected. Should be \c{>= 1}.
+
+ \value SnapPositionRatio This is the distance that the user must drag the area beween two snap
+ points in order to snap it to the next position. \c{0.33} means that the scroll must only
+ reach one third of the distance between two snap points to snap to the next one. The ratio must
+ be between \c 0 and \c 1.
+
+ \value SnapTime This is the time factor for the scrolling curve. A lower value means that the
+ scrolling will take longer. The scrolling distance is independet of this value.
+
+ \value OvershootDragResistanceFactor This value is the factor between the mouse dragging and
+ the actual scroll area movement (during overshoot). The factor must be between \c 0 and \c 1.
+
+ \value OvershootDragDistanceFactor This is the maximum distance for overshoot movements while
+ dragging. The actual overshoot distance is calculated by multiplying this value with the
+ viewport size of the scrolled object. The factor must be between \c 0 and \c 1.
+
+ \value OvershootScrollDistanceFactor This is the maximum distance for overshoot movements while
+ scrolling. The actual overshoot distance is calculated by multiplying this value with the
+ viewport size of the scrolled object. The factor must be between \c 0 and \c 1.
+
+ \value OvershootScrollTime This is the time in \c seconds that is used to play the
+ complete overshoot animation.
+
+ \value HorizontalOvershootPolicy This is the horizontal overshooting policy (see OvershootPolicy).
+
+ \value VerticalOvershootPolicy This is the horizontal overshooting policy (see OvershootPolicy).
+
+ \value FrameRate This is the frame rate which should be used while dragging or scrolling.
+ QScroller uses a QAbstractAnimation timer internally to sync all scrolling operations to other
+ animations that might be active at the same time. If the standard value of 60 frames per
+ second is too fast, it can be lowered with this setting,
+ while still being in-sync with QAbstractAnimation. Please note that only the values of the
+ FrameRates enum are allowed here.
+
+ \value ScrollMetricCount This is always the last entry.
+*/
+
+QT_END_NAMESPACE
diff --git a/src/widgets/util/qscrollerproperties.h b/src/widgets/util/qscrollerproperties.h
new file mode 100644
index 0000000000..73c1125fe0
--- /dev/null
+++ b/src/widgets/util/qscrollerproperties.h
@@ -0,0 +1,140 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSCROLLERPROPERTIES_H
+#define QSCROLLERPROPERTIES_H
+
+#include <QtCore/QScopedPointer>
+#include <QtCore/QMetaType>
+#include <QtCore/QVariant>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Gui)
+
+class QScroller;
+class QScrollerPrivate;
+class QScrollerPropertiesPrivate;
+
+class Q_WIDGETS_EXPORT QScrollerProperties
+{
+public:
+ QScrollerProperties();
+ QScrollerProperties(const QScrollerProperties &sp);
+ QScrollerProperties &operator=(const QScrollerProperties &sp);
+ virtual ~QScrollerProperties();
+
+ bool operator==(const QScrollerProperties &sp) const;
+ bool operator!=(const QScrollerProperties &sp) const;
+
+ static void setDefaultScrollerProperties(const QScrollerProperties &sp);
+ static void unsetDefaultScrollerProperties();
+
+ enum OvershootPolicy
+ {
+ OvershootWhenScrollable,
+ OvershootAlwaysOff,
+ OvershootAlwaysOn
+ };
+
+ enum FrameRates {
+ Standard,
+ Fps60,
+ Fps30,
+ Fps20
+ };
+
+ enum ScrollMetric
+ {
+ MousePressEventDelay, // qreal [s]
+ DragStartDistance, // qreal [m]
+ DragVelocitySmoothingFactor, // qreal [0..1/s] (complex calculation involving time) v = v_new* DASF + v_old * (1-DASF)
+ AxisLockThreshold, // qreal [0..1] atan(|min(dx,dy)|/|max(dx,dy)|)
+
+ ScrollingCurve, // QEasingCurve
+ DecelerationFactor, // slope of the curve
+
+ MinimumVelocity, // qreal [m/s]
+ MaximumVelocity, // qreal [m/s]
+ MaximumClickThroughVelocity, // qreal [m/s]
+
+ AcceleratingFlickMaximumTime, // qreal [s]
+ AcceleratingFlickSpeedupFactor, // qreal [1..]
+
+ SnapPositionRatio, // qreal [0..1]
+ SnapTime, // qreal [s]
+
+ OvershootDragResistanceFactor, // qreal [0..1]
+ OvershootDragDistanceFactor, // qreal [0..1]
+ OvershootScrollDistanceFactor, // qreal [0..1]
+ OvershootScrollTime, // qreal [s]
+
+ HorizontalOvershootPolicy, // enum OvershootPolicy
+ VerticalOvershootPolicy, // enum OvershootPolicy
+ FrameRate, // enum FrameRates
+
+ ScrollMetricCount
+ };
+
+ QVariant scrollMetric(ScrollMetric metric) const;
+ void setScrollMetric(ScrollMetric metric, const QVariant &value);
+
+protected:
+ QScopedPointer<QScrollerPropertiesPrivate> d;
+
+private:
+ QScrollerProperties(QScrollerPropertiesPrivate &dd);
+
+ friend class QScrollerPropertiesPrivate;
+ friend class QScroller;
+ friend class QScrollerPrivate;
+};
+
+QT_END_NAMESPACE
+
+Q_DECLARE_METATYPE(QScrollerProperties::OvershootPolicy)
+Q_DECLARE_METATYPE(QScrollerProperties::FrameRates)
+
+QT_END_HEADER
+
+#endif // QSCROLLERPROPERTIES_H
diff --git a/src/widgets/util/qscrollerproperties_p.h b/src/widgets/util/qscrollerproperties_p.h
new file mode 100644
index 0000000000..2e57a3f7dd
--- /dev/null
+++ b/src/widgets/util/qscrollerproperties_p.h
@@ -0,0 +1,94 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSCROLLERPROPERTIES_P_H
+#define QSCROLLERPROPERTIES_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 <QPointF>
+#include <QEasingCurve>
+#include <qscrollerproperties.h>
+
+QT_BEGIN_NAMESPACE
+
+class QScrollerPropertiesPrivate
+{
+public:
+ static QScrollerPropertiesPrivate *defaults();
+
+ bool operator==(const QScrollerPropertiesPrivate &) const;
+
+ qreal mousePressEventDelay;
+ qreal dragStartDistance;
+ qreal dragVelocitySmoothingFactor;
+ qreal axisLockThreshold;
+ QEasingCurve scrollingCurve;
+ qreal decelerationFactor;
+ qreal minimumVelocity;
+ qreal maximumVelocity;
+ qreal maximumClickThroughVelocity;
+ qreal acceleratingFlickMaximumTime;
+ qreal acceleratingFlickSpeedupFactor;
+ qreal snapPositionRatio;
+ qreal snapTime;
+ qreal overshootDragResistanceFactor;
+ qreal overshootDragDistanceFactor;
+ qreal overshootScrollDistanceFactor;
+ qreal overshootScrollTime;
+ QScrollerProperties::OvershootPolicy hOvershootPolicy;
+ QScrollerProperties::OvershootPolicy vOvershootPolicy;
+ QScrollerProperties::FrameRates frameRate;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSCROLLERPROPERTIES_P_H
+
diff --git a/src/widgets/util/qsystemtrayicon.cpp b/src/widgets/util/qsystemtrayicon.cpp
new file mode 100644
index 0000000000..f6eeaa02b2
--- /dev/null
+++ b/src/widgets/util/qsystemtrayicon.cpp
@@ -0,0 +1,674 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsystemtrayicon.h"
+#include "qsystemtrayicon_p.h"
+
+#ifndef QT_NO_SYSTEMTRAYICON
+
+#include "qmenu.h"
+#include "qevent.h"
+#include "qpoint.h"
+#include "qlabel.h"
+#include "qpushbutton.h"
+#include "qpainterpath.h"
+#include "qpainter.h"
+#include "qstyle.h"
+#include "qgridlayout.h"
+#include "qapplication.h"
+#include "qdesktopwidget.h"
+#include "qbitmap.h"
+#include "private/qlabel_p.h"
+#include "qapplication.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QSystemTrayIcon
+ \brief The QSystemTrayIcon class provides an icon for an application in the system tray.
+ \since 4.2
+ \ingroup desktop
+
+ Modern operating systems usually provide a special area on the desktop,
+ called the \e{system tray} or \e{notification area}, where long-running
+ applications can display icons and short messages.
+
+ \image system-tray.png The system tray on Windows XP.
+
+ The QSystemTrayIcon class can be used on the following platforms:
+
+ \list
+ \o All supported versions of Windows.
+ \o All window managers for X11 that implement the \l{freedesktop.org} system
+ tray specification, including recent versions of KDE and GNOME.
+ \o All supported versions of Mac OS X. Note that the Growl
+ notification system must be installed for
+ QSystemTrayIcon::showMessage() to display messages.
+ \endlist
+
+ To check whether a system tray is present on the user's desktop,
+ call the QSystemTrayIcon::isSystemTrayAvailable() static function.
+
+ To add a system tray entry, create a QSystemTrayIcon object, call setContextMenu()
+ to provide a context menu for the icon, and call show() to make it visible in the
+ system tray. Status notification messages ("balloon messages") can be displayed at
+ any time using showMessage().
+
+ If the system tray is unavailable when a system tray icon is constructed, but
+ becomes available later, QSystemTrayIcon will automatically add an entry for the
+ application in the system tray if the icon is \l visible.
+
+ The activated() signal is emitted when the user activates the icon.
+
+ Only on X11, when a tooltip is requested, the QSystemTrayIcon receives a QHelpEvent
+ of type QEvent::ToolTip. Additionally, the QSystemTrayIcon receives wheel events of
+ type QEvent::Wheel. These are not supported on any other platform.
+
+ \sa QDesktopServices, QDesktopWidget, {Desktop Integration}, {System Tray Icon Example}
+*/
+
+/*!
+ \enum QSystemTrayIcon::MessageIcon
+
+ This enum describes the icon that is shown when a balloon message is displayed.
+
+ \value NoIcon No icon is shown.
+ \value Information An information icon is shown.
+ \value Warning A standard warning icon is shown.
+ \value Critical A critical warning icon is shown.
+
+ \sa QMessageBox
+*/
+
+/*!
+ Constructs a QSystemTrayIcon object with the given \a parent.
+
+ The icon is initially invisible.
+
+ \sa visible
+*/
+QSystemTrayIcon::QSystemTrayIcon(QObject *parent)
+: QObject(*new QSystemTrayIconPrivate(), parent)
+{
+}
+
+/*!
+ Constructs a QSystemTrayIcon object with the given \a icon and \a parent.
+
+ The icon is initially invisible.
+
+ \sa visible
+*/
+QSystemTrayIcon::QSystemTrayIcon(const QIcon &icon, QObject *parent)
+: QObject(*new QSystemTrayIconPrivate(), parent)
+{
+ setIcon(icon);
+}
+
+/*!
+ Removes the icon from the system tray and frees all allocated resources.
+*/
+QSystemTrayIcon::~QSystemTrayIcon()
+{
+ Q_D(QSystemTrayIcon);
+ d->remove_sys();
+}
+
+#ifndef QT_NO_MENU
+
+/*!
+ Sets the specified \a menu to be the context menu for the system tray icon.
+
+ The menu will pop up when the user requests the context menu for the system
+ tray icon by clicking the mouse button.
+
+ On Mac OS X, this is currenly converted to a NSMenu, so the
+ aboutToHide() signal is not emitted.
+
+ \note The system tray icon does not take ownership of the menu. You must
+ ensure that it is deleted at the appropriate time by, for example, creating
+ the menu with a suitable parent object.
+*/
+void QSystemTrayIcon::setContextMenu(QMenu *menu)
+{
+ Q_D(QSystemTrayIcon);
+ d->menu = menu;
+ d->updateMenu_sys();
+}
+
+/*!
+ Returns the current context menu for the system tray entry.
+*/
+QMenu* QSystemTrayIcon::contextMenu() const
+{
+ Q_D(const QSystemTrayIcon);
+ return d->menu;
+}
+
+#endif // QT_NO_MENU
+
+/*!
+ \property QSystemTrayIcon::icon
+ \brief the system tray icon
+
+ On Windows, the system tray icon size is 16x16; on X11, the preferred size is
+ 22x22. The icon will be scaled to the appropriate size as necessary.
+*/
+void QSystemTrayIcon::setIcon(const QIcon &icon)
+{
+ Q_D(QSystemTrayIcon);
+ d->icon = icon;
+ d->updateIcon_sys();
+}
+
+QIcon QSystemTrayIcon::icon() const
+{
+ Q_D(const QSystemTrayIcon);
+ return d->icon;
+}
+
+/*!
+ \property QSystemTrayIcon::toolTip
+ \brief the tooltip for the system tray entry
+
+ On some systems, the tooltip's length is limited. The tooltip will be truncated
+ if necessary.
+*/
+void QSystemTrayIcon::setToolTip(const QString &tooltip)
+{
+ Q_D(QSystemTrayIcon);
+ d->toolTip = tooltip;
+ d->updateToolTip_sys();
+}
+
+QString QSystemTrayIcon::toolTip() const
+{
+ Q_D(const QSystemTrayIcon);
+ return d->toolTip;
+}
+
+/*!
+ \fn void QSystemTrayIcon::show()
+
+ Shows the icon in the system tray.
+
+ \sa hide(), visible
+*/
+
+/*!
+ \fn void QSystemTrayIcon::hide()
+
+ Hides the system tray entry.
+
+ \sa show(), visible
+*/
+
+/*!
+ \since 4.3
+ Returns the geometry of the system tray icon in screen coordinates.
+
+ \sa visible
+*/
+QRect QSystemTrayIcon::geometry() const
+{
+ Q_D(const QSystemTrayIcon);
+ if (!d->visible)
+ return QRect();
+ return d->geometry_sys();
+}
+
+/*!
+ \property QSystemTrayIcon::visible
+ \brief whether the system tray entry is visible
+
+ Setting this property to true or calling show() makes the system tray icon
+ visible; setting this property to false or calling hide() hides it.
+*/
+void QSystemTrayIcon::setVisible(bool visible)
+{
+ Q_D(QSystemTrayIcon);
+ if (visible == d->visible)
+ return;
+ if (d->icon.isNull() && visible)
+ qWarning("QSystemTrayIcon::setVisible: No Icon set");
+ d->visible = visible;
+ if (d->visible)
+ d->install_sys();
+ else
+ d->remove_sys();
+}
+
+bool QSystemTrayIcon::isVisible() const
+{
+ Q_D(const QSystemTrayIcon);
+ return d->visible;
+}
+
+/*!
+ \reimp
+*/
+bool QSystemTrayIcon::event(QEvent *e)
+{
+#if defined(Q_WS_X11)
+ if (e->type() == QEvent::ToolTip) {
+ Q_D(QSystemTrayIcon);
+ return d->sys->deliverToolTipEvent(e);
+ }
+#endif
+ return QObject::event(e);
+}
+
+/*!
+ \enum QSystemTrayIcon::ActivationReason
+
+ This enum describes the reason the system tray was activated.
+
+ \value Unknown Unknown reason
+ \value Context The context menu for the system tray entry was requested
+ \value DoubleClick The system tray entry was double clicked
+ \value Trigger The system tray entry was clicked
+ \value MiddleClick The system tray entry was clicked with the middle mouse button
+
+ \sa activated()
+*/
+
+/*!
+ \fn void QSystemTrayIcon::activated(QSystemTrayIcon::ActivationReason reason)
+
+ This signal is emitted when the user activates the system tray icon. \a reason
+ specifies the reason for activation. QSystemTrayIcon::ActivationReason enumerates
+ the various reasons.
+
+ \sa QSystemTrayIcon::ActivationReason
+*/
+
+/*!
+ \fn void QSystemTrayIcon::messageClicked()
+
+ This signal is emitted when the message displayed using showMessage()
+ was clicked by the user.
+
+ Currently this signal is not sent on Mac OS X.
+
+ \note We follow Microsoft Windows XP/Vista behavior, so the
+ signal is also emitted when the user clicks on a tray icon with
+ a balloon message displayed.
+
+ \sa activated()
+*/
+
+
+/*!
+ Returns true if the system tray is available; otherwise returns false.
+
+ If the system tray is currently unavailable but becomes available later,
+ QSystemTrayIcon will automatically add an entry in the system tray if it
+ is \l visible.
+*/
+
+bool QSystemTrayIcon::isSystemTrayAvailable()
+{
+ return QSystemTrayIconPrivate::isSystemTrayAvailable_sys();
+}
+
+/*!
+ Returns true if the system tray supports balloon messages; otherwise returns false.
+
+ \sa showMessage()
+*/
+bool QSystemTrayIcon::supportsMessages()
+{
+ return QSystemTrayIconPrivate::supportsMessages_sys();
+}
+
+/*!
+ \fn void QSystemTrayIcon::showMessage(const QString &title, const QString &message, MessageIcon icon, int millisecondsTimeoutHint)
+ \since 4.3
+
+ Shows a balloon message for the entry with the given \a title, \a message and
+ \a icon for the time specified in \a millisecondsTimeoutHint. \a title and \a message
+ must be plain text strings.
+
+ Message can be clicked by the user; the messageClicked() signal will emitted when
+ this occurs.
+
+ Note that display of messages are dependent on the system configuration and user
+ preferences, and that messages may not appear at all. Hence, it should not be
+ relied upon as the sole means for providing critical information.
+
+ On Windows, the \a millisecondsTimeoutHint is usually ignored by the system
+ when the application has focus.
+
+ On Mac OS X, the Growl notification system must be installed for this function to
+ display messages.
+
+ \sa show() supportsMessages()
+ */
+void QSystemTrayIcon::showMessage(const QString& title, const QString& msg,
+ QSystemTrayIcon::MessageIcon icon, int msecs)
+{
+ Q_D(QSystemTrayIcon);
+ if (d->visible)
+ d->showMessage_sys(title, msg, icon, msecs);
+}
+
+//////////////////////////////////////////////////////////////////////
+static QBalloonTip *theSolitaryBalloonTip = 0;
+
+void QBalloonTip::showBalloon(QSystemTrayIcon::MessageIcon icon, const QString& title,
+ const QString& message, QSystemTrayIcon *trayIcon,
+ const QPoint& pos, int timeout, bool showArrow)
+{
+ hideBalloon();
+ if (message.isEmpty() && title.isEmpty())
+ return;
+
+ theSolitaryBalloonTip = new QBalloonTip(icon, title, message, trayIcon);
+ if (timeout < 0)
+ timeout = 10000; //10 s default
+ theSolitaryBalloonTip->balloon(pos, timeout, showArrow);
+}
+
+void QBalloonTip::hideBalloon()
+{
+ if (!theSolitaryBalloonTip)
+ return;
+ theSolitaryBalloonTip->hide();
+ delete theSolitaryBalloonTip;
+ theSolitaryBalloonTip = 0;
+}
+
+bool QBalloonTip::isBalloonVisible()
+{
+ return theSolitaryBalloonTip;
+}
+
+QBalloonTip::QBalloonTip(QSystemTrayIcon::MessageIcon icon, const QString& title,
+ const QString& message, QSystemTrayIcon *ti)
+ : QWidget(0, Qt::ToolTip), trayIcon(ti), timerId(-1)
+{
+ setAttribute(Qt::WA_DeleteOnClose);
+ QObject::connect(ti, SIGNAL(destroyed()), this, SLOT(close()));
+
+ QLabel *titleLabel = new QLabel;
+ titleLabel->installEventFilter(this);
+ titleLabel->setText(title);
+ QFont f = titleLabel->font();
+ f.setBold(true);
+#ifdef Q_WS_WINCE
+ f.setPointSize(f.pointSize() - 2);
+#endif
+ titleLabel->setFont(f);
+ titleLabel->setTextFormat(Qt::PlainText); // to maintain compat with windows
+
+#ifdef Q_WS_WINCE
+ const int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize);
+ const int closeButtonSize = style()->pixelMetric(QStyle::PM_SmallIconSize) - 2;
+#else
+ const int iconSize = 18;
+ const int closeButtonSize = 15;
+#endif
+
+ QPushButton *closeButton = new QPushButton;
+ closeButton->setIcon(style()->standardIcon(QStyle::SP_TitleBarCloseButton));
+ closeButton->setIconSize(QSize(closeButtonSize, closeButtonSize));
+ closeButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ closeButton->setFixedSize(closeButtonSize, closeButtonSize);
+ QObject::connect(closeButton, SIGNAL(clicked()), this, SLOT(close()));
+
+ QLabel *msgLabel = new QLabel;
+#ifdef Q_WS_WINCE
+ f.setBold(false);
+ msgLabel->setFont(f);
+#endif
+ msgLabel->installEventFilter(this);
+ msgLabel->setText(message);
+ msgLabel->setTextFormat(Qt::PlainText);
+ msgLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft);
+
+ // smart size for the message label
+#ifdef Q_WS_WINCE
+ int limit = QApplication::desktop()->availableGeometry(msgLabel).size().width() / 2;
+#else
+ int limit = QApplication::desktop()->availableGeometry(msgLabel).size().width() / 3;
+#endif
+ if (msgLabel->sizeHint().width() > limit) {
+ msgLabel->setWordWrap(true);
+ if (msgLabel->sizeHint().width() > limit) {
+ msgLabel->d_func()->ensureTextControl();
+ if (QWidgetTextControl *control = msgLabel->d_func()->control) {
+ QTextOption opt = control->document()->defaultTextOption();
+ opt.setWrapMode(QTextOption::WrapAnywhere);
+ control->document()->setDefaultTextOption(opt);
+ }
+ }
+#ifdef Q_WS_WINCE
+ // Make sure that the text isn't wrapped "somewhere" in the balloon widget
+ // in the case that we have a long title label.
+ setMaximumWidth(limit);
+#else
+ // Here we allow the text being much smaller than the balloon widget
+ // to emulate the weird standard windows behavior.
+ msgLabel->setFixedSize(limit, msgLabel->heightForWidth(limit));
+#endif
+ }
+
+ QIcon si;
+ switch (icon) {
+ case QSystemTrayIcon::Warning:
+ si = style()->standardIcon(QStyle::SP_MessageBoxWarning);
+ break;
+ case QSystemTrayIcon::Critical:
+ si = style()->standardIcon(QStyle::SP_MessageBoxCritical);
+ break;
+ case QSystemTrayIcon::Information:
+ si = style()->standardIcon(QStyle::SP_MessageBoxInformation);
+ break;
+ case QSystemTrayIcon::NoIcon:
+ default:
+ break;
+ }
+
+ QGridLayout *layout = new QGridLayout;
+ if (!si.isNull()) {
+ QLabel *iconLabel = new QLabel;
+ iconLabel->setPixmap(si.pixmap(iconSize, iconSize));
+ iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ iconLabel->setMargin(2);
+ layout->addWidget(iconLabel, 0, 0);
+ layout->addWidget(titleLabel, 0, 1);
+ } else {
+ layout->addWidget(titleLabel, 0, 0, 1, 2);
+ }
+
+ layout->addWidget(closeButton, 0, 2);
+ layout->addWidget(msgLabel, 1, 0, 1, 3);
+ layout->setSizeConstraint(QLayout::SetFixedSize);
+ layout->setMargin(3);
+ setLayout(layout);
+
+ QPalette pal = palette();
+ pal.setColor(QPalette::Window, QColor(0xff, 0xff, 0xe1));
+ pal.setColor(QPalette::WindowText, Qt::black);
+ setPalette(pal);
+}
+
+QBalloonTip::~QBalloonTip()
+{
+ theSolitaryBalloonTip = 0;
+}
+
+void QBalloonTip::paintEvent(QPaintEvent *)
+{
+ QPainter painter(this);
+ painter.drawPixmap(rect(), pixmap);
+}
+
+void QBalloonTip::resizeEvent(QResizeEvent *ev)
+{
+ QWidget::resizeEvent(ev);
+}
+
+void QBalloonTip::balloon(const QPoint& pos, int msecs, bool showArrow)
+{
+ QRect scr = QApplication::desktop()->screenGeometry(pos);
+ QSize sh = sizeHint();
+ const int border = 1;
+ const int ah = 18, ao = 18, aw = 18, rc = 7;
+ bool arrowAtTop = (pos.y() + sh.height() + ah < scr.height());
+ bool arrowAtLeft = (pos.x() + sh.width() - ao < scr.width());
+ setContentsMargins(border + 3, border + (arrowAtTop ? ah : 0) + 2, border + 3, border + (arrowAtTop ? 0 : ah) + 2);
+ updateGeometry();
+ sh = sizeHint();
+
+ int ml, mr, mt, mb;
+ QSize sz = sizeHint();
+ if (!arrowAtTop) {
+ ml = mt = 0;
+ mr = sz.width() - 1;
+ mb = sz.height() - ah - 1;
+ } else {
+ ml = 0;
+ mt = ah;
+ mr = sz.width() - 1;
+ mb = sz.height() - 1;
+ }
+
+ QPainterPath path;
+#if defined(QT_NO_XSHAPE) && defined(Q_WS_X11)
+ // XShape is required for setting the mask, so we just
+ // draw an ugly square when its not available
+ path.moveTo(0, 0);
+ path.lineTo(sz.width() - 1, 0);
+ path.lineTo(sz.width() - 1, sz.height() - 1);
+ path.lineTo(0, sz.height() - 1);
+ path.lineTo(0, 0);
+ move(qMax(pos.x() - sz.width(), scr.left()), pos.y());
+#else
+ path.moveTo(ml + rc, mt);
+ if (arrowAtTop && arrowAtLeft) {
+ if (showArrow) {
+ path.lineTo(ml + ao, mt);
+ path.lineTo(ml + ao, mt - ah);
+ path.lineTo(ml + ao + aw, mt);
+ }
+ move(qMax(pos.x() - ao, scr.left() + 2), pos.y());
+ } else if (arrowAtTop && !arrowAtLeft) {
+ if (showArrow) {
+ path.lineTo(mr - ao - aw, mt);
+ path.lineTo(mr - ao, mt - ah);
+ path.lineTo(mr - ao, mt);
+ }
+ move(qMin(pos.x() - sh.width() + ao, scr.right() - sh.width() - 2), pos.y());
+ }
+ path.lineTo(mr - rc, mt);
+ path.arcTo(QRect(mr - rc*2, mt, rc*2, rc*2), 90, -90);
+ path.lineTo(mr, mb - rc);
+ path.arcTo(QRect(mr - rc*2, mb - rc*2, rc*2, rc*2), 0, -90);
+ if (!arrowAtTop && !arrowAtLeft) {
+ if (showArrow) {
+ path.lineTo(mr - ao, mb);
+ path.lineTo(mr - ao, mb + ah);
+ path.lineTo(mr - ao - aw, mb);
+ }
+ move(qMin(pos.x() - sh.width() + ao, scr.right() - sh.width() - 2),
+ pos.y() - sh.height());
+ } else if (!arrowAtTop && arrowAtLeft) {
+ if (showArrow) {
+ path.lineTo(ao + aw, mb);
+ path.lineTo(ao, mb + ah);
+ path.lineTo(ao, mb);
+ }
+ move(qMax(pos.x() - ao, scr.x() + 2), pos.y() - sh.height());
+ }
+ path.lineTo(ml + rc, mb);
+ path.arcTo(QRect(ml, mb - rc*2, rc*2, rc*2), -90, -90);
+ path.lineTo(ml, mt + rc);
+ path.arcTo(QRect(ml, mt, rc*2, rc*2), 180, -90);
+
+ // Set the mask
+ QBitmap bitmap = QBitmap(sizeHint());
+ bitmap.fill(Qt::color0);
+ QPainter painter1(&bitmap);
+ painter1.setPen(QPen(Qt::color1, border));
+ painter1.setBrush(QBrush(Qt::color1));
+ painter1.drawPath(path);
+ setMask(bitmap);
+#endif
+
+ // Draw the border
+ pixmap = QPixmap(sz);
+ QPainter painter2(&pixmap);
+ painter2.setPen(QPen(palette().color(QPalette::Window).darker(160), border));
+ painter2.setBrush(palette().color(QPalette::Window));
+ painter2.drawPath(path);
+
+ if (msecs > 0)
+ timerId = startTimer(msecs);
+ show();
+}
+
+void QBalloonTip::mousePressEvent(QMouseEvent *e)
+{
+ close();
+ if(e->button() == Qt::LeftButton)
+ emit trayIcon->messageClicked();
+}
+
+void QBalloonTip::timerEvent(QTimerEvent *e)
+{
+ if (e->timerId() == timerId) {
+ killTimer(timerId);
+ if (!underMouse())
+ close();
+ return;
+ }
+ QWidget::timerEvent(e);
+}
+
+void qtsystray_sendActivated(QSystemTrayIcon *i, int r)
+{
+ emit i->activated((QSystemTrayIcon::ActivationReason)r);
+}
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_SYSTEMTRAYICON
diff --git a/src/widgets/util/qsystemtrayicon.h b/src/widgets/util/qsystemtrayicon.h
new file mode 100644
index 0000000000..3327ce4223
--- /dev/null
+++ b/src/widgets/util/qsystemtrayicon.h
@@ -0,0 +1,132 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSYSTEMTRAYICON_H
+#define QSYSTEMTRAYICON_H
+
+#include <QtCore/qobject.h>
+
+#ifndef QT_NO_SYSTEMTRAYICON
+
+#include <QtWidgets/qicon.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Gui)
+
+class QSystemTrayIconPrivate;
+
+class QMenu;
+class QEvent;
+class QWheelEvent;
+class QMouseEvent;
+class QPoint;
+
+class Q_WIDGETS_EXPORT QSystemTrayIcon : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString toolTip READ toolTip WRITE setToolTip)
+ Q_PROPERTY(QIcon icon READ icon WRITE setIcon)
+ Q_PROPERTY(bool visible READ isVisible WRITE setVisible DESIGNABLE false)
+
+public:
+ QSystemTrayIcon(QObject *parent = 0);
+ QSystemTrayIcon(const QIcon &icon, QObject *parent = 0);
+ ~QSystemTrayIcon();
+
+ enum ActivationReason {
+ Unknown,
+ Context,
+ DoubleClick,
+ Trigger,
+ MiddleClick
+ };
+
+#ifndef QT_NO_MENU
+ void setContextMenu(QMenu *menu);
+ QMenu *contextMenu() const;
+#endif
+
+ QIcon icon() const;
+ void setIcon(const QIcon &icon);
+
+ QString toolTip() const;
+ void setToolTip(const QString &tip);
+
+ static bool isSystemTrayAvailable();
+ static bool supportsMessages();
+
+ enum MessageIcon { NoIcon, Information, Warning, Critical };
+ void showMessage(const QString &title, const QString &msg,
+ MessageIcon icon = Information, int msecs = 10000);
+
+ QRect geometry() const;
+ bool isVisible() const;
+
+public Q_SLOTS:
+ void setVisible(bool visible);
+ inline void show() { setVisible(true); }
+ inline void hide() { setVisible(false); }
+
+Q_SIGNALS:
+ void activated(QSystemTrayIcon::ActivationReason reason);
+ void messageClicked();
+
+protected:
+ bool event(QEvent *event);
+
+private:
+ Q_DISABLE_COPY(QSystemTrayIcon)
+ Q_DECLARE_PRIVATE(QSystemTrayIcon)
+
+ friend class QSystemTrayIconSys;
+ friend class QBalloonTip;
+ friend void qtsystray_sendActivated(QSystemTrayIcon *, int);
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QT_NO_SYSTEMTRAYICON
+#endif // QSYSTEMTRAYICON_H
diff --git a/src/widgets/util/qsystemtrayicon_mac.mm b/src/widgets/util/qsystemtrayicon_mac.mm
new file mode 100644
index 0000000000..4186ac3e55
--- /dev/null
+++ b/src/widgets/util/qsystemtrayicon_mac.mm
@@ -0,0 +1,578 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/****************************************************************************
+**
+** Copyright (c) 2007-2008, Apple, Inc.
+**
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+**
+** * Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** * Redistributions in binary form must reproduce the above copyright notice,
+** this list of conditions and the following disclaimer in the documentation
+** and/or other materials provided with the distribution.
+**
+** * Neither the name of Apple, Inc. nor the names of its contributors
+** may be used to endorse or promote products derived from this software
+** without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+****************************************************************************/
+
+#define QT_MAC_SYSTEMTRAY_USE_GROWL
+
+#include <private/qt_cocoa_helpers_mac_p.h>
+#include <private/qsystemtrayicon_p.h>
+#include <qtemporaryfile.h>
+#include <qimagewriter.h>
+#include <qapplication.h>
+#include <qdebug.h>
+#include <qstyle.h>
+
+#include <private/qt_mac_p.h>
+#import <AppKit/AppKit.h>
+
+QT_BEGIN_NAMESPACE
+extern bool qt_mac_execute_apple_script(const QString &script, AEDesc *ret); //qapplication_mac.cpp
+extern void qtsystray_sendActivated(QSystemTrayIcon *i, int r); //qsystemtrayicon.cpp
+extern NSString *keySequenceToKeyEqivalent(const QKeySequence &accel); // qmenu_mac.mm
+extern NSUInteger keySequenceModifierMask(const QKeySequence &accel); // qmenu_mac.mm
+extern Qt::MouseButton cocoaButton2QtButton(NSInteger buttonNum);
+QT_END_NAMESPACE
+
+QT_USE_NAMESPACE
+
+@class QT_MANGLE_NAMESPACE(QNSMenu);
+@class QT_MANGLE_NAMESPACE(QNSImageView);
+
+@interface QT_MANGLE_NAMESPACE(QNSStatusItem) : NSObject {
+ NSStatusItem *item;
+ QSystemTrayIcon *icon;
+ QSystemTrayIconPrivate *iconPrivate;
+ QT_MANGLE_NAMESPACE(QNSImageView) *imageCell;
+}
+-(id)initWithIcon:(QSystemTrayIcon*)icon iconPrivate:(QSystemTrayIconPrivate *)iprivate;
+-(void)dealloc;
+-(QSystemTrayIcon*)icon;
+-(NSStatusItem*)item;
+-(QRectF)geometry;
+- (void)triggerSelector:(id)sender button:(Qt::MouseButton)mouseButton;
+- (void)doubleClickSelector:(id)sender;
+@end
+
+@interface QT_MANGLE_NAMESPACE(QNSImageView) : NSImageView {
+ BOOL down;
+ QT_MANGLE_NAMESPACE(QNSStatusItem) *parent;
+}
+-(id)initWithParent:(QT_MANGLE_NAMESPACE(QNSStatusItem)*)myParent;
+-(QSystemTrayIcon*)icon;
+-(void)menuTrackingDone:(NSNotification*)notification;
+-(void)mousePressed:(NSEvent *)mouseEvent button:(Qt::MouseButton)mouseButton;
+@end
+
+
+#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5
+
+@protocol NSMenuDelegate <NSObject>
+-(void)menuNeedsUpdate:(NSMenu*)menu;
+@end
+#endif
+
+
+@interface QT_MANGLE_NAMESPACE(QNSMenu) : NSMenu <NSMenuDelegate> {
+ QMenu *qmenu;
+}
+-(QMenu*)menu;
+-(id)initWithQMenu:(QMenu*)qmenu;
+-(void)selectedAction:(id)item;
+@end
+
+QT_BEGIN_NAMESPACE
+class QSystemTrayIconSys
+{
+public:
+ QSystemTrayIconSys(QSystemTrayIcon *icon, QSystemTrayIconPrivate *d) {
+ QMacCocoaAutoReleasePool pool;
+ item = [[QT_MANGLE_NAMESPACE(QNSStatusItem) alloc] initWithIcon:icon iconPrivate:d];
+ }
+ ~QSystemTrayIconSys() {
+ QMacCocoaAutoReleasePool pool;
+ [[[item item] view] setHidden: YES];
+ [item release];
+ }
+ QT_MANGLE_NAMESPACE(QNSStatusItem) *item;
+};
+
+void QSystemTrayIconPrivate::install_sys()
+{
+ Q_Q(QSystemTrayIcon);
+ if (!sys) {
+ sys = new QSystemTrayIconSys(q, this);
+ updateIcon_sys();
+ updateMenu_sys();
+ updateToolTip_sys();
+ }
+}
+
+QRect QSystemTrayIconPrivate::geometry_sys() const
+{
+ if(sys) {
+ const QRectF geom = [sys->item geometry];
+ if(!geom.isNull())
+ return geom.toRect();
+ }
+ return QRect();
+}
+
+void QSystemTrayIconPrivate::remove_sys()
+{
+ delete sys;
+ sys = 0;
+}
+
+void QSystemTrayIconPrivate::updateIcon_sys()
+{
+ if(sys && !icon.isNull()) {
+ QMacCocoaAutoReleasePool pool;
+#ifndef QT_MAC_USE_COCOA
+ const short scale = GetMBarHeight()-4;
+#else
+ CGFloat hgt = [[[NSApplication sharedApplication] mainMenu] menuBarHeight];
+ const short scale = hgt - 4;
+#endif
+ NSImage *nsimage = static_cast<NSImage *>(qt_mac_create_nsimage(icon.pixmap(QSize(scale, scale))));
+ [(NSImageView*)[[sys->item item] view] setImage: nsimage];
+ [nsimage release];
+ }
+}
+
+void QSystemTrayIconPrivate::updateMenu_sys()
+{
+ if(sys) {
+ QMacCocoaAutoReleasePool pool;
+ if(menu && !menu->isEmpty()) {
+ [[sys->item item] setHighlightMode:YES];
+ } else {
+ [[sys->item item] setHighlightMode:NO];
+ }
+ }
+}
+
+void QSystemTrayIconPrivate::updateToolTip_sys()
+{
+ if(sys) {
+ QMacCocoaAutoReleasePool pool;
+ QCFString string(toolTip);
+ [[[sys->item item] view] setToolTip:(NSString*)static_cast<CFStringRef>(string)];
+ }
+}
+
+bool QSystemTrayIconPrivate::isSystemTrayAvailable_sys()
+{
+ return true;
+}
+
+bool QSystemTrayIconPrivate::supportsMessages_sys()
+{
+ return true;
+}
+
+void QSystemTrayIconPrivate::showMessage_sys(const QString &title, const QString &message, QSystemTrayIcon::MessageIcon icon, int)
+{
+
+ if(sys) {
+#ifdef QT_MAC_SYSTEMTRAY_USE_GROWL
+ // Make sure that we have Growl installed on the machine we are running on.
+ QCFType<CFURLRef> cfurl;
+ OSStatus status = LSGetApplicationForInfo(kLSUnknownType, kLSUnknownCreator,
+ CFSTR("growlTicket"), kLSRolesAll, 0, &cfurl);
+ if (status == kLSApplicationNotFoundErr)
+ return;
+ QCFType<CFBundleRef> bundle = CFBundleCreate(0, cfurl);
+
+ if (CFStringCompare(CFBundleGetIdentifier(bundle), CFSTR("com.Growl.GrowlHelperApp"),
+ kCFCompareCaseInsensitive | kCFCompareBackwards) != kCFCompareEqualTo)
+ return;
+ QPixmap notificationIconPixmap;
+ if(icon == QSystemTrayIcon::Information)
+ notificationIconPixmap = QApplication::style()->standardPixmap(QStyle::SP_MessageBoxInformation);
+ else if(icon == QSystemTrayIcon::Warning)
+ notificationIconPixmap = QApplication::style()->standardPixmap(QStyle::SP_MessageBoxWarning);
+ else if(icon == QSystemTrayIcon::Critical)
+ notificationIconPixmap = QApplication::style()->standardPixmap(QStyle::SP_MessageBoxCritical);
+ QTemporaryFile notificationIconFile;
+ QString notificationType(QLatin1String("Notification")), notificationIcon, notificationApp(QApplication::applicationName());
+ if(notificationApp.isEmpty())
+ notificationApp = QLatin1String("Application");
+ if(!notificationIconPixmap.isNull() && notificationIconFile.open()) {
+ QImageWriter writer(&notificationIconFile, "PNG");
+ if(writer.write(notificationIconPixmap.toImage()))
+ notificationIcon = QLatin1String("image from location \"file://") + notificationIconFile.fileName() + QLatin1String("\"");
+ }
+ const QString script(QLatin1String(
+ "tell application \"GrowlHelperApp\"\n"
+ "-- Make a list of all the notification types (all)\n"
+ "set the allNotificationsList to {\"") + notificationType + QLatin1String("\"}\n"
+
+ "-- Make a list of the notifications (enabled)\n"
+ "set the enabledNotificationsList to {\"") + notificationType + QLatin1String("\"}\n"
+
+ "-- Register our script with growl.\n"
+ "register as application \"") + notificationApp + QLatin1String("\" all notifications allNotificationsList default notifications enabledNotificationsList\n"
+
+ "-- Send a Notification...\n") +
+ QLatin1String("notify with name \"") + notificationType +
+ QLatin1String("\" title \"") + title +
+ QLatin1String("\" description \"") + message +
+ QLatin1String("\" application name \"") + notificationApp +
+ QLatin1String("\" ") + notificationIcon +
+ QLatin1String("\nend tell"));
+ qt_mac_execute_apple_script(script, 0);
+#elif 0
+ Q_Q(QSystemTrayIcon);
+ NSView *v = [[sys->item item] view];
+ NSWindow *w = [v window];
+ w = [[sys->item item] window];
+ qDebug() << w << v;
+ QPoint p(qRound([w frame].origin.x), qRound([w frame].origin.y));
+ qDebug() << p;
+ QBalloonTip::showBalloon(icon, message, title, q, QPoint(0, 0), msecs);
+#else
+ Q_UNUSED(icon);
+ Q_UNUSED(title);
+ Q_UNUSED(message);
+#endif
+ }
+}
+QT_END_NAMESPACE
+
+@implementation NSStatusItem (Qt)
+@end
+
+@implementation QT_MANGLE_NAMESPACE(QNSImageView)
+-(id)initWithParent:(QT_MANGLE_NAMESPACE(QNSStatusItem)*)myParent {
+ self = [super init];
+ parent = myParent;
+ down = NO;
+ return self;
+}
+
+-(QSystemTrayIcon*)icon {
+ return [parent icon];
+}
+
+-(void)menuTrackingDone:(NSNotification*)notification
+{
+ Q_UNUSED(notification);
+ down = NO;
+
+ if( ![self icon]->icon().isNull() ) {
+#ifndef QT_MAC_USE_COCOA
+ const short scale = GetMBarHeight()-4;
+#else
+ CGFloat hgt = [[[NSApplication sharedApplication] mainMenu] menuBarHeight];
+ const short scale = hgt - 4;
+#endif
+ NSImage *nsimage = static_cast<NSImage *>(qt_mac_create_nsimage([self icon]->icon().pixmap(QSize(scale, scale))));
+ [self setImage: nsimage];
+ [nsimage release];
+ }
+
+ if([self icon]->contextMenu())
+ [self icon]->contextMenu()->hide();
+
+ [self setNeedsDisplay:YES];
+}
+
+-(void)mousePressed:(NSEvent *)mouseEvent button:(Qt::MouseButton)mouseButton
+{
+ down = YES;
+ int clickCount = [mouseEvent clickCount];
+ [self setNeedsDisplay:YES];
+
+#ifndef QT_MAC_USE_COCOA
+ const short scale = GetMBarHeight()-4;
+#else
+ CGFloat hgt = [[[NSApplication sharedApplication] mainMenu] menuBarHeight];
+ const short scale = hgt - 4;
+#endif
+
+ if (![self icon]->icon().isNull() ) {
+ NSImage *nsaltimage = static_cast<NSImage *>(qt_mac_create_nsimage([self icon]->icon().pixmap(QSize(scale, scale), QIcon::Selected)));
+ [self setImage: nsaltimage];
+ [nsaltimage release];
+ }
+
+ if ((clickCount == 2)) {
+ [self menuTrackingDone:nil];
+ [parent doubleClickSelector:self];
+ } else {
+ [parent triggerSelector:self button:mouseButton];
+ }
+}
+
+-(void)mouseDown:(NSEvent *)mouseEvent
+{
+ [self mousePressed:mouseEvent button:Qt::LeftButton];
+}
+
+-(void)mouseUp:(NSEvent *)mouseEvent
+{
+ Q_UNUSED(mouseEvent);
+ [self menuTrackingDone:nil];
+}
+
+- (void)rightMouseDown:(NSEvent *)mouseEvent
+{
+ [self mousePressed:mouseEvent button:Qt::RightButton];
+}
+
+-(void)rightMouseUp:(NSEvent *)mouseEvent
+{
+ Q_UNUSED(mouseEvent);
+ [self menuTrackingDone:nil];
+}
+
+- (void)otherMouseDown:(NSEvent *)mouseEvent
+{
+ [self mousePressed:mouseEvent button:cocoaButton2QtButton([mouseEvent buttonNumber])];
+}
+
+-(void)otherMouseUp:(NSEvent *)mouseEvent
+{
+ Q_UNUSED(mouseEvent);
+ [self menuTrackingDone:nil];
+}
+
+-(void)drawRect:(NSRect)rect {
+ [[parent item] drawStatusBarBackgroundInRect:rect withHighlight:down];
+ [super drawRect:rect];
+}
+@end
+
+@implementation QT_MANGLE_NAMESPACE(QNSStatusItem)
+
+-(id)initWithIcon:(QSystemTrayIcon*)i iconPrivate:(QSystemTrayIconPrivate *)iPrivate
+{
+ self = [super init];
+ if(self) {
+ icon = i;
+ iconPrivate = iPrivate;
+ item = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
+ imageCell = [[QT_MANGLE_NAMESPACE(QNSImageView) alloc] initWithParent:self];
+ [item setView: imageCell];
+ }
+ return self;
+}
+-(void)dealloc {
+ [[NSStatusBar systemStatusBar] removeStatusItem:item];
+ [imageCell release];
+ [item release];
+ [super dealloc];
+
+}
+
+-(QSystemTrayIcon*)icon {
+ return icon;
+}
+
+-(NSStatusItem*)item {
+ return item;
+}
+-(QRectF)geometry {
+ if(NSWindow *window = [[item view] window]) {
+ NSRect screenRect = [[window screen] frame];
+ NSRect windowRect = [window frame];
+ return QRectF(windowRect.origin.x, screenRect.size.height-windowRect.origin.y-windowRect.size.height, windowRect.size.width, windowRect.size.height);
+ }
+ return QRectF();
+}
+
+- (void)triggerSelector:(id)sender button:(Qt::MouseButton)mouseButton {
+ Q_UNUSED(sender);
+ if (!icon)
+ return;
+
+ if (mouseButton == Qt::MidButton)
+ qtsystray_sendActivated(icon, QSystemTrayIcon::MiddleClick);
+ else
+ qtsystray_sendActivated(icon, QSystemTrayIcon::Trigger);
+
+ if (icon->contextMenu()) {
+#ifndef QT_MAC_USE_COCOA
+ [[[self item] view] removeAllToolTips];
+ iconPrivate->updateToolTip_sys();
+#endif
+ NSMenu *m = [[QT_MANGLE_NAMESPACE(QNSMenu) alloc] initWithQMenu:icon->contextMenu()];
+ [m setAutoenablesItems: NO];
+ [[NSNotificationCenter defaultCenter] addObserver:imageCell
+ selector:@selector(menuTrackingDone:)
+ name:NSMenuDidEndTrackingNotification
+ object:m];
+ [item popUpStatusItemMenu: m];
+ [m release];
+ }
+}
+
+- (void)doubleClickSelector:(id)sender {
+ Q_UNUSED(sender);
+ if(!icon)
+ return;
+ qtsystray_sendActivated(icon, QSystemTrayIcon::DoubleClick);
+}
+
+@end
+
+class QSystemTrayIconQMenu : public QMenu
+{
+public:
+ void doAboutToShow() { emit aboutToShow(); }
+private:
+ QSystemTrayIconQMenu();
+};
+
+@implementation QT_MANGLE_NAMESPACE(QNSMenu)
+-(id)initWithQMenu:(QMenu*)qm {
+ self = [super init];
+ if(self) {
+ self->qmenu = qm;
+ [self setDelegate:self];
+ }
+ return self;
+}
+-(QMenu*)menu {
+ return qmenu;
+}
+-(void)menuNeedsUpdate:(NSMenu*)nsmenu {
+ QT_MANGLE_NAMESPACE(QNSMenu) *menu = static_cast<QT_MANGLE_NAMESPACE(QNSMenu) *>(nsmenu);
+ emit static_cast<QSystemTrayIconQMenu*>(menu->qmenu)->doAboutToShow();
+ for(int i = [menu numberOfItems]-1; i >= 0; --i)
+ [menu removeItemAtIndex:i];
+ QList<QAction*> actions = menu->qmenu->actions();;
+ for(int i = 0; i < actions.size(); ++i) {
+ const QAction *action = actions[i];
+ if(!action->isVisible())
+ continue;
+
+ NSMenuItem *item = 0;
+ bool needRelease = false;
+ if(action->isSeparator()) {
+ item = [NSMenuItem separatorItem];
+ } else {
+ item = [[NSMenuItem alloc] init];
+ needRelease = true;
+ QString text = action->text();
+ QKeySequence accel = action->shortcut();
+ {
+ int st = text.lastIndexOf(QLatin1Char('\t'));
+ if(st != -1) {
+ accel = QKeySequence(text.right(text.length()-(st+1)));
+ text.remove(st, text.length()-st);
+ }
+ }
+ if(accel.count() > 1)
+ text += QLatin1String(" (****)"); //just to denote a multi stroke shortcut
+
+ [item setTitle:(NSString*)QCFString::toCFStringRef(qt_mac_removeMnemonics(text))];
+ [item setEnabled:menu->qmenu->isEnabled() && action->isEnabled()];
+ [item setState:action->isChecked() ? NSOnState : NSOffState];
+ [item setToolTip:(NSString*)QCFString::toCFStringRef(action->toolTip())];
+ const QIcon icon = action->icon();
+ if(!icon.isNull()) {
+#ifndef QT_MAC_USE_COCOA
+ const short scale = GetMBarHeight();
+#else
+ const short scale = [[NSApp mainMenu] menuBarHeight];
+#endif
+ NSImage *nsimage = static_cast<NSImage *>(qt_mac_create_nsimage(icon.pixmap(QSize(scale, scale))));
+ [item setImage: nsimage];
+ [nsimage release];
+ }
+ if(action->menu()) {
+ QT_MANGLE_NAMESPACE(QNSMenu) *sub = [[QT_MANGLE_NAMESPACE(QNSMenu) alloc] initWithQMenu:action->menu()];
+ [item setSubmenu:sub];
+ } else {
+ [item setAction:@selector(selectedAction:)];
+ [item setTarget:self];
+ }
+ if(!accel.isEmpty()) {
+ [item setKeyEquivalent:keySequenceToKeyEqivalent(accel)];
+ [item setKeyEquivalentModifierMask:keySequenceModifierMask(accel)];
+ }
+ }
+ if(item)
+ [menu addItem:item];
+ if (needRelease)
+ [item release];
+ }
+}
+-(void)selectedAction:(id)a {
+ const int activated = [self indexOfItem:a];
+ QAction *action = 0;
+ QList<QAction*> actions = qmenu->actions();
+ for(int i = 0, cnt = 0; i < actions.size(); ++i) {
+ if(actions.at(i)->isVisible() && (cnt++) == activated) {
+ action = actions.at(i);
+ break;
+ }
+ }
+ if(action) {
+ action->activate(QAction::Trigger);
+ }
+}
+@end
+
diff --git a/src/widgets/util/qsystemtrayicon_p.h b/src/widgets/util/qsystemtrayicon_p.h
new file mode 100644
index 0000000000..ce2404ae8b
--- /dev/null
+++ b/src/widgets/util/qsystemtrayicon_p.h
@@ -0,0 +1,186 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSYSTEMTRAYICON_P_H
+#define QSYSTEMTRAYICON_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qsystemtrayicon.h"
+#include "private/qobject_p.h"
+
+#ifndef QT_NO_SYSTEMTRAYICON
+
+#include "QtWidgets/qmenu.h"
+#include "QtGui/qpixmap.h"
+#include "QtCore/qstring.h"
+#include "QtCore/qpointer.h"
+
+QT_BEGIN_NAMESPACE
+
+class QSystemTrayIconSys;
+class QToolButton;
+class QLabel;
+
+class QSystemTrayIconPrivate : public QObjectPrivate
+{
+ Q_DECLARE_PUBLIC(QSystemTrayIcon)
+
+public:
+ QSystemTrayIconPrivate() : sys(0), visible(false) { }
+
+ void install_sys();
+ void remove_sys();
+ void updateIcon_sys();
+ void updateToolTip_sys();
+ void updateMenu_sys();
+ QRect geometry_sys() const;
+ void showMessage_sys(const QString &msg, const QString &title, QSystemTrayIcon::MessageIcon icon, int secs);
+
+ static bool isSystemTrayAvailable_sys();
+ static bool supportsMessages_sys();
+
+ QPointer<QMenu> menu;
+ QIcon icon;
+ QString toolTip;
+ QSystemTrayIconSys *sys;
+ bool visible;
+};
+
+class QBalloonTip : public QWidget
+{
+ Q_OBJECT
+public:
+ static void showBalloon(QSystemTrayIcon::MessageIcon icon, const QString& title,
+ const QString& msg, QSystemTrayIcon *trayIcon,
+ const QPoint& pos, int timeout, bool showArrow = true);
+ static void hideBalloon();
+ static bool isBalloonVisible();
+
+private:
+ QBalloonTip(QSystemTrayIcon::MessageIcon icon, const QString& title,
+ const QString& msg, QSystemTrayIcon *trayIcon);
+ ~QBalloonTip();
+ void balloon(const QPoint&, int, bool);
+
+protected:
+ void paintEvent(QPaintEvent *);
+ void resizeEvent(QResizeEvent *);
+ void mousePressEvent(QMouseEvent *e);
+ void timerEvent(QTimerEvent *e);
+
+private:
+ QSystemTrayIcon *trayIcon;
+ QPixmap pixmap;
+ int timerId;
+};
+
+#if defined(Q_WS_X11)
+QT_BEGIN_INCLUDE_NAMESPACE
+#include <QtCore/qcoreapplication.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/Xutil.h>
+QT_END_INCLUDE_NAMESPACE
+
+class QSystemTrayIconSys : public QWidget
+{
+ friend class QSystemTrayIconPrivate;
+
+public:
+ QSystemTrayIconSys(QSystemTrayIcon *q);
+ ~QSystemTrayIconSys();
+ enum {
+ SYSTEM_TRAY_REQUEST_DOCK = 0,
+ SYSTEM_TRAY_BEGIN_MESSAGE = 1,
+ SYSTEM_TRAY_CANCEL_MESSAGE =2
+ };
+
+ void addToTray();
+ void updateIcon();
+ XVisualInfo* getSysTrayVisualInfo();
+
+ // QObject::event is public but QWidget's ::event() re-implementation
+ // is protected ;(
+ inline bool deliverToolTipEvent(QEvent *e)
+ { return QWidget::event(e); }
+
+ static Window sysTrayWindow;
+ static QList<QSystemTrayIconSys *> trayIcons;
+ static QCoreApplication::EventFilter oldEventFilter;
+ static bool sysTrayTracker(void *message, long *result);
+ static Window locateSystemTray();
+ static Atom sysTraySelection;
+ static XVisualInfo sysTrayVisual;
+
+protected:
+ void paintEvent(QPaintEvent *pe);
+ void resizeEvent(QResizeEvent *re);
+ bool x11Event(XEvent *event);
+ void mousePressEvent(QMouseEvent *event);
+ void mouseDoubleClickEvent(QMouseEvent *event);
+#ifndef QT_NO_WHEELEVENT
+ void wheelEvent(QWheelEvent *event);
+#endif
+ bool event(QEvent *e);
+
+private:
+ QPixmap background;
+ QSystemTrayIcon *q;
+ Colormap colormap;
+};
+#endif // Q_WS_X11
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_SYSTEMTRAYICON
+
+#endif // QSYSTEMTRAYICON_P_H
+
diff --git a/src/widgets/util/qsystemtrayicon_qpa.cpp b/src/widgets/util/qsystemtrayicon_qpa.cpp
new file mode 100644
index 0000000000..9a4f12d02d
--- /dev/null
+++ b/src/widgets/util/qsystemtrayicon_qpa.cpp
@@ -0,0 +1,96 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsystemtrayicon_p.h"
+
+#ifndef QT_NO_SYSTEMTRAYICON
+
+QT_BEGIN_NAMESPACE
+
+void QSystemTrayIconPrivate::install_sys()
+{
+}
+
+void QSystemTrayIconPrivate::remove_sys()
+{
+}
+
+QRect QSystemTrayIconPrivate::geometry_sys() const
+{
+ return QRect();
+}
+
+void QSystemTrayIconPrivate::updateIcon_sys()
+{
+}
+
+void QSystemTrayIconPrivate::updateMenu_sys()
+{
+}
+
+void QSystemTrayIconPrivate::updateToolTip_sys()
+{
+}
+
+bool QSystemTrayIconPrivate::isSystemTrayAvailable_sys()
+{
+ return false;
+}
+
+bool QSystemTrayIconPrivate::supportsMessages_sys()
+{
+ return false;
+}
+
+void QSystemTrayIconPrivate::showMessage_sys(const QString &message,
+ const QString &title,
+ QSystemTrayIcon::MessageIcon icon,
+ int msecs)
+{
+ Q_UNUSED(message);
+ Q_UNUSED(title);
+ Q_UNUSED(icon);
+ Q_UNUSED(msecs);
+}
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_SYSTEMTRAYICON
diff --git a/src/widgets/util/qsystemtrayicon_win.cpp b/src/widgets/util/qsystemtrayicon_win.cpp
new file mode 100644
index 0000000000..7a572849b0
--- /dev/null
+++ b/src/widgets/util/qsystemtrayicon_win.cpp
@@ -0,0 +1,524 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsystemtrayicon_p.h"
+#ifndef QT_NO_SYSTEMTRAYICON
+
+#ifndef _WIN32_WINNT
+#define _WIN32_WINNT 0x0600
+#endif
+
+#ifndef _WIN32_IE
+#define _WIN32_IE 0x600
+#endif
+
+#include <qt_windows.h>
+#include <windowsx.h>
+#include <commctrl.h>
+
+#include <private/qsystemlibrary_p.h>
+#include <QApplication>
+#include <QSettings>
+
+QT_BEGIN_NAMESPACE
+
+static const UINT q_uNOTIFYICONID = 0;
+
+static uint MYWM_TASKBARCREATED = 0;
+#define MYWM_NOTIFYICON (WM_APP+101)
+
+struct Q_NOTIFYICONIDENTIFIER {
+ DWORD cbSize;
+ HWND hWnd;
+ UINT uID;
+ GUID guidItem;
+};
+
+#ifndef NOTIFYICON_VERSION_4
+#define NOTIFYICON_VERSION_4 4
+#endif
+
+#ifndef NIN_SELECT
+#define NIN_SELECT (WM_USER + 0)
+#endif
+
+#ifndef NIN_KEYSELECT
+#define NIN_KEYSELECT (WM_USER + 1)
+#endif
+
+#ifndef NIN_BALLOONTIMEOUT
+#define NIN_BALLOONTIMEOUT (WM_USER + 4)
+#endif
+
+#ifndef NIN_BALLOONUSERCLICK
+#define NIN_BALLOONUSERCLICK (WM_USER + 5)
+#endif
+
+#ifndef NIF_SHOWTIP
+#define NIF_SHOWTIP 0x00000080
+#endif
+
+#define Q_MSGFLT_ALLOW 1
+
+typedef HRESULT (WINAPI *PtrShell_NotifyIconGetRect)(const Q_NOTIFYICONIDENTIFIER* identifier, RECT* iconLocation);
+typedef BOOL (WINAPI *PtrChangeWindowMessageFilter)(UINT message, DWORD dwFlag);
+typedef BOOL (WINAPI *PtrChangeWindowMessageFilterEx)(HWND hWnd, UINT message, DWORD action, void* pChangeFilterStruct);
+
+class QSystemTrayIconSys : QWidget
+{
+public:
+ QSystemTrayIconSys(QSystemTrayIcon *object);
+ ~QSystemTrayIconSys();
+ bool winEvent( MSG *m, long *result );
+ bool trayMessage(DWORD msg);
+ void setIconContents(NOTIFYICONDATA &data);
+ bool showMessage(const QString &title, const QString &message, QSystemTrayIcon::MessageIcon type, uint uSecs);
+ QRect findIconGeometry(const int a_iButtonID);
+ void createIcon();
+ HICON hIcon;
+ QPoint globalPos;
+ QSystemTrayIcon *q;
+private:
+ uint notifyIconSize;
+ int maxTipLength;
+ int version;
+ bool ignoreNextMouseRelease;
+};
+
+static bool allowsMessages()
+{
+#ifndef QT_NO_SETTINGS
+ QSettings settings(QLatin1String("HKEY_CURRENT_USER\\Software\\Microsoft"
+ "\\Windows\\CurrentVersion\\Explorer\\Advanced"), QSettings::NativeFormat);
+ return settings.value(QLatin1String("EnableBalloonTips"), true).toBool();
+#else
+ return false;
+#endif
+}
+
+QSystemTrayIconSys::QSystemTrayIconSys(QSystemTrayIcon *object)
+ : hIcon(0), q(object), ignoreNextMouseRelease(false)
+
+{
+ if (QSysInfo::windowsVersion() >= QSysInfo::WV_VISTA) {
+ notifyIconSize = sizeof(NOTIFYICONDATA);
+ version = NOTIFYICON_VERSION_4;
+ } else {
+ notifyIconSize = NOTIFYICONDATA_V2_SIZE;
+ version = NOTIFYICON_VERSION;
+ }
+
+ maxTipLength = 128;
+
+ // For restoring the tray icon after explorer crashes
+ if (!MYWM_TASKBARCREATED) {
+ MYWM_TASKBARCREATED = RegisterWindowMessage(L"TaskbarCreated");
+ }
+
+ // Allow the WM_TASKBARCREATED message through the UIPI filter on Windows Vista and higher
+ static PtrChangeWindowMessageFilterEx pChangeWindowMessageFilterEx =
+ (PtrChangeWindowMessageFilterEx)QSystemLibrary::resolve(QLatin1String("user32"), "ChangeWindowMessageFilterEx");
+
+ if (pChangeWindowMessageFilterEx) {
+ // Call the safer ChangeWindowMessageFilterEx API if available
+ pChangeWindowMessageFilterEx(winId(), MYWM_TASKBARCREATED, Q_MSGFLT_ALLOW, 0);
+ } else {
+ static PtrChangeWindowMessageFilter pChangeWindowMessageFilter =
+ (PtrChangeWindowMessageFilter)QSystemLibrary::resolve(QLatin1String("user32"), "ChangeWindowMessageFilter");
+
+ if (pChangeWindowMessageFilter) {
+ // Call the deprecated ChangeWindowMessageFilter API otherwise
+ pChangeWindowMessageFilter(MYWM_TASKBARCREATED, Q_MSGFLT_ALLOW);
+ }
+ }
+}
+
+QSystemTrayIconSys::~QSystemTrayIconSys()
+{
+ if (hIcon)
+ DestroyIcon(hIcon);
+}
+
+void QSystemTrayIconSys::setIconContents(NOTIFYICONDATA &tnd)
+{
+ tnd.uFlags |= NIF_MESSAGE | NIF_ICON | NIF_TIP;
+ tnd.uCallbackMessage = MYWM_NOTIFYICON;
+ tnd.hIcon = hIcon;
+ QString tip = q->toolTip();
+
+ if (!tip.isNull()) {
+ tip = tip.left(maxTipLength - 1) + QChar();
+ memcpy(tnd.szTip, tip.utf16(), qMin(tip.length() + 1, maxTipLength) * sizeof(wchar_t));
+ }
+}
+
+static int iconFlag( QSystemTrayIcon::MessageIcon icon )
+{
+ switch (icon) {
+ case QSystemTrayIcon::Information:
+ return NIIF_INFO;
+ case QSystemTrayIcon::Warning:
+ return NIIF_WARNING;
+ case QSystemTrayIcon::Critical:
+ return NIIF_ERROR;
+ case QSystemTrayIcon::NoIcon:
+ return NIIF_NONE;
+ default:
+ Q_ASSERT_X(false, "QSystemTrayIconSys::showMessage", "Invalid QSystemTrayIcon::MessageIcon value");
+ return NIIF_NONE;
+ }
+}
+
+bool QSystemTrayIconSys::showMessage(const QString &title, const QString &message, QSystemTrayIcon::MessageIcon type, uint uSecs)
+{
+ NOTIFYICONDATA tnd;
+ memset(&tnd, 0, notifyIconSize);
+
+ memcpy(tnd.szInfo, message.utf16(), qMin(message.length() + 1, 256) * sizeof(wchar_t));
+ memcpy(tnd.szInfoTitle, title.utf16(), qMin(title.length() + 1, 64) * sizeof(wchar_t));
+
+ tnd.uID = q_uNOTIFYICONID;
+ tnd.dwInfoFlags = iconFlag(type);
+ tnd.cbSize = notifyIconSize;
+ tnd.hWnd = winId();
+ tnd.uTimeout = uSecs;
+ tnd.uFlags = NIF_INFO | NIF_SHOWTIP;
+
+ Q_ASSERT(testAttribute(Qt::WA_WState_Created));
+
+ return Shell_NotifyIcon(NIM_MODIFY, &tnd);
+}
+
+bool QSystemTrayIconSys::trayMessage(DWORD msg)
+{
+ NOTIFYICONDATA tnd;
+ memset(&tnd, 0, notifyIconSize);
+
+ tnd.uID = q_uNOTIFYICONID;
+ tnd.cbSize = notifyIconSize;
+ tnd.hWnd = winId();
+ tnd.uFlags = NIF_SHOWTIP;
+ tnd.uVersion = version;
+
+ Q_ASSERT(testAttribute(Qt::WA_WState_Created));
+
+ if (msg == NIM_ADD || msg == NIM_MODIFY) {
+ setIconContents(tnd);
+ }
+
+ bool success = Shell_NotifyIcon(msg, &tnd);
+
+ if (msg == NIM_ADD)
+ return success && Shell_NotifyIcon(NIM_SETVERSION, &tnd);
+ else
+ return success;
+}
+
+void QSystemTrayIconSys::createIcon()
+{
+ hIcon = 0;
+ QIcon icon = q->icon();
+ if (icon.isNull())
+ return;
+
+ const int iconSizeX = GetSystemMetrics(SM_CXSMICON);
+ const int iconSizeY = GetSystemMetrics(SM_CYSMICON);
+ QSize size = icon.actualSize(QSize(iconSizeX, iconSizeY));
+ QPixmap pm = icon.pixmap(size);
+ if (pm.isNull())
+ return;
+
+ hIcon = pm.toWinHICON();
+}
+
+bool QSystemTrayIconSys::winEvent( MSG *m, long *result )
+{
+ switch(m->message) {
+ case MYWM_NOTIFYICON:
+ {
+ int message = 0;
+ QPoint gpos;
+
+ if (version == NOTIFYICON_VERSION_4) {
+ Q_ASSERT(q_uNOTIFYICONID == HIWORD(m->lParam));
+ message = LOWORD(m->lParam);
+ gpos = QPoint(GET_X_LPARAM(m->wParam), GET_Y_LPARAM(m->wParam));
+ } else {
+ Q_ASSERT(q_uNOTIFYICONID == m->wParam);
+ message = m->lParam;
+ gpos = QCursor::pos();
+ }
+
+ switch (message) {
+ case NIN_SELECT:
+ case NIN_KEYSELECT:
+ if (ignoreNextMouseRelease)
+ ignoreNextMouseRelease = false;
+ else
+ emit q->activated(QSystemTrayIcon::Trigger);
+ break;
+
+ case WM_LBUTTONDBLCLK:
+ ignoreNextMouseRelease = true; // Since DBLCLICK Generates a second mouse
+ // release we must ignore it
+ emit q->activated(QSystemTrayIcon::DoubleClick);
+ break;
+
+ case WM_CONTEXTMENU:
+ if (q->contextMenu()) {
+ q->contextMenu()->popup(gpos);
+ q->contextMenu()->activateWindow();
+ }
+ emit q->activated(QSystemTrayIcon::Context);
+ break;
+
+ case NIN_BALLOONUSERCLICK:
+ emit q->messageClicked();
+ break;
+
+ case WM_MBUTTONUP:
+ emit q->activated(QSystemTrayIcon::MiddleClick);
+ break;
+
+ default:
+ break;
+ }
+ break;
+ }
+ default:
+ if (m->message == MYWM_TASKBARCREATED)
+ trayMessage(NIM_ADD);
+ else
+ return QWidget::winEvent(m, result);
+ break;
+ }
+ return 0;
+}
+
+void QSystemTrayIconPrivate::install_sys()
+{
+ Q_Q(QSystemTrayIcon);
+ if (!sys) {
+ sys = new QSystemTrayIconSys(q);
+ sys->createIcon();
+ sys->trayMessage(NIM_ADD);
+ }
+}
+
+/*
+* This function tries to determine the icon geometry from the tray
+*
+* If it fails an invalid rect is returned.
+*/
+QRect QSystemTrayIconSys::findIconGeometry(const int iconId)
+{
+ static PtrShell_NotifyIconGetRect Shell_NotifyIconGetRect =
+ (PtrShell_NotifyIconGetRect)QSystemLibrary::resolve(QLatin1String("shell32"), "Shell_NotifyIconGetRect");
+
+ if (Shell_NotifyIconGetRect) {
+ Q_NOTIFYICONIDENTIFIER nid;
+ memset(&nid, 0, sizeof(nid));
+ nid.cbSize = sizeof(nid);
+ nid.hWnd = winId();
+ nid.uID = iconId;
+
+ RECT rect;
+ HRESULT hr = Shell_NotifyIconGetRect(&nid, &rect);
+ if (SUCCEEDED(hr)) {
+ return QRect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
+ }
+ }
+
+ QRect ret;
+
+ TBBUTTON buttonData;
+ DWORD processID = 0;
+ HWND trayHandle = FindWindow(L"Shell_TrayWnd", NULL);
+
+ //find the toolbar used in the notification area
+ if (trayHandle) {
+ trayHandle = FindWindowEx(trayHandle, NULL, L"TrayNotifyWnd", NULL);
+ if (trayHandle) {
+ HWND hwnd = FindWindowEx(trayHandle, NULL, L"SysPager", NULL);
+ if (hwnd) {
+ hwnd = FindWindowEx(hwnd, NULL, L"ToolbarWindow32", NULL);
+ if (hwnd)
+ trayHandle = hwnd;
+ }
+ }
+ }
+
+ if (!trayHandle)
+ return ret;
+
+ GetWindowThreadProcessId(trayHandle, &processID);
+ if (processID <= 0)
+ return ret;
+
+ HANDLE trayProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ, 0, processID);
+ if (!trayProcess)
+ return ret;
+
+ int buttonCount = SendMessage(trayHandle, TB_BUTTONCOUNT, 0, 0);
+ LPVOID data = VirtualAllocEx(trayProcess, NULL, sizeof(TBBUTTON), MEM_COMMIT, PAGE_READWRITE);
+
+ if ( buttonCount < 1 || !data ) {
+ CloseHandle(trayProcess);
+ return ret;
+ }
+
+ //search for our icon among all toolbar buttons
+ for (int toolbarButton = 0; toolbarButton < buttonCount; ++toolbarButton ) {
+ SIZE_T numBytes = 0;
+ DWORD appData[2] = { 0, 0 };
+ SendMessage(trayHandle, TB_GETBUTTON, toolbarButton , (LPARAM)data);
+
+ if (!ReadProcessMemory(trayProcess, data, &buttonData, sizeof(TBBUTTON), &numBytes))
+ continue;
+
+ if (!ReadProcessMemory(trayProcess, (LPVOID) buttonData.dwData, appData, sizeof(appData), &numBytes))
+ continue;
+
+ int currentIconId = appData[1];
+ HWND currentIconHandle = (HWND) appData[0];
+ bool isHidden = buttonData.fsState & TBSTATE_HIDDEN;
+
+ if (currentIconHandle == winId() &&
+ currentIconId == iconId && !isHidden) {
+ SendMessage(trayHandle, TB_GETITEMRECT, toolbarButton , (LPARAM)data);
+ RECT iconRect = {0, 0};
+ if(ReadProcessMemory(trayProcess, data, &iconRect, sizeof(RECT), &numBytes)) {
+ MapWindowPoints(trayHandle, NULL, (LPPOINT)&iconRect, 2);
+ QRect geometry(iconRect.left + 1, iconRect.top + 1,
+ iconRect.right - iconRect.left - 2,
+ iconRect.bottom - iconRect.top - 2);
+ if (geometry.isValid())
+ ret = geometry;
+ break;
+ }
+ }
+ }
+ VirtualFreeEx(trayProcess, data, 0, MEM_RELEASE);
+ CloseHandle(trayProcess);
+ return ret;
+}
+
+void QSystemTrayIconPrivate::showMessage_sys(const QString &title, const QString &message, QSystemTrayIcon::MessageIcon type, int timeOut)
+{
+ if (!sys || !allowsMessages())
+ return;
+
+ uint uSecs = 0;
+ if ( timeOut < 0)
+ uSecs = 10000; //10 sec default
+ else uSecs = (int)timeOut;
+
+ //message is limited to 255 chars + NULL
+ QString messageString;
+ if (message.isEmpty() && !title.isEmpty())
+ messageString = QLatin1Char(' '); //ensures that the message shows when only title is set
+ else
+ messageString = message.left(255) + QChar();
+
+ //title is limited to 63 chars + NULL
+ QString titleString = title.left(63) + QChar();
+
+ sys->showMessage(titleString, messageString, type, uSecs);
+}
+
+QRect QSystemTrayIconPrivate::geometry_sys() const
+{
+ if (!sys)
+ return QRect();
+
+ return sys->findIconGeometry(q_uNOTIFYICONID);
+}
+
+void QSystemTrayIconPrivate::remove_sys()
+{
+ if (!sys)
+ return;
+
+ sys->trayMessage(NIM_DELETE);
+ delete sys;
+ sys = 0;
+}
+
+void QSystemTrayIconPrivate::updateIcon_sys()
+{
+ if (!sys)
+ return;
+
+ HICON hIconToDestroy = sys->hIcon;
+
+ sys->createIcon();
+ sys->trayMessage(NIM_MODIFY);
+
+ if (hIconToDestroy)
+ DestroyIcon(hIconToDestroy);
+}
+
+void QSystemTrayIconPrivate::updateMenu_sys()
+{
+
+}
+
+void QSystemTrayIconPrivate::updateToolTip_sys()
+{
+ if (!sys)
+ return;
+
+ sys->trayMessage(NIM_MODIFY);
+}
+
+bool QSystemTrayIconPrivate::isSystemTrayAvailable_sys()
+{
+ return true;
+}
+
+bool QSystemTrayIconPrivate::supportsMessages_sys()
+{
+ return allowsMessages();
+}
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/widgets/util/qsystemtrayicon_wince.cpp b/src/widgets/util/qsystemtrayicon_wince.cpp
new file mode 100644
index 0000000000..7a403a3ee5
--- /dev/null
+++ b/src/widgets/util/qsystemtrayicon_wince.cpp
@@ -0,0 +1,293 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsystemtrayicon_p.h"
+#ifndef QT_NO_SYSTEMTRAYICON
+#define _WIN32_IE 0x0600 //required for NOTIFYICONDATA_V2_SIZE
+
+#include <qt_windows.h>
+#include <shlwapi.h>
+#include <QApplication>
+
+QT_BEGIN_NAMESPACE
+
+static const UINT q_uNOTIFYICONID = 13; // IDs from 0 to 12 are reserved on WinCE.
+#define MYWM_NOTIFYICON (WM_APP+101)
+
+struct Q_NOTIFYICONIDENTIFIER {
+ DWORD cbSize;
+ HWND hWnd;
+ UINT uID;
+ GUID guidItem;
+};
+
+class QSystemTrayIconSys : QWidget
+{
+public:
+ QSystemTrayIconSys(QSystemTrayIcon *object);
+ ~QSystemTrayIconSys();
+ bool winEvent( MSG *m, long *result );
+ bool trayMessage(DWORD msg);
+ void setIconContents(NOTIFYICONDATA &data);
+ void createIcon();
+ QRect findTrayGeometry();
+ HICON hIcon;
+ QPoint globalPos;
+ QSystemTrayIcon *q;
+private:
+ uint notifyIconSize;
+ int maxTipLength;
+ bool ignoreNextMouseRelease;
+};
+
+QSystemTrayIconSys::QSystemTrayIconSys(QSystemTrayIcon *object)
+ : hIcon(0), q(object), ignoreNextMouseRelease(false)
+
+{
+ notifyIconSize = FIELD_OFFSET(NOTIFYICONDATA, szTip[64]); // NOTIFYICONDATAW_V1_SIZE;
+ maxTipLength = 64;
+}
+
+QSystemTrayIconSys::~QSystemTrayIconSys()
+{
+ if (hIcon)
+ DestroyIcon(hIcon);
+}
+
+QRect QSystemTrayIconSys::findTrayGeometry()
+{
+ // Use lower right corner as fallback
+ QPoint brCorner = qApp->desktop()->screenGeometry().bottomRight();
+ QRect ret(brCorner.x() - 10, brCorner.y() - 10, 10, 10);
+ return ret;
+}
+
+void QSystemTrayIconSys::setIconContents(NOTIFYICONDATA &tnd)
+{
+ tnd.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
+ tnd.uCallbackMessage = MYWM_NOTIFYICON;
+ tnd.hIcon = hIcon;
+ QString tip = q->toolTip();
+
+ if (!tip.isNull()) {
+ tip = tip.left(maxTipLength - 1) + QChar();
+ memcpy(tnd.szTip, tip.utf16(), qMin(tip.length() + 1, maxTipLength) * sizeof(wchar_t));
+ }
+}
+
+bool QSystemTrayIconSys::trayMessage(DWORD msg)
+{
+ NOTIFYICONDATA tnd;
+ memset(&tnd, 0, notifyIconSize);
+ tnd.uID = q_uNOTIFYICONID;
+ tnd.cbSize = notifyIconSize;
+ tnd.hWnd = winId();
+
+ Q_ASSERT(testAttribute(Qt::WA_WState_Created));
+
+ if (msg != NIM_DELETE) {
+ setIconContents(tnd);
+ }
+
+ return Shell_NotifyIcon(msg, &tnd);
+}
+
+void QSystemTrayIconSys::createIcon()
+{
+ hIcon = 0;
+ QIcon icon = q->icon();
+ if (icon.isNull())
+ return;
+
+ //const QSize preferredSize(GetSystemMetrics(SM_CXSMICON) * 2, GetSystemMetrics(SM_CYSMICON) * 2);
+ const QSize preferredSize(GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON));
+ QPixmap pm = icon.pixmap(preferredSize);
+ if (pm.isNull())
+ return;
+
+ hIcon = pm.toWinHICON();
+}
+
+bool QSystemTrayIconSys::winEvent( MSG *m, long *result )
+{
+ switch(m->message) {
+ case WM_CREATE:
+ SetWindowLong(winId(), GWL_USERDATA, (LONG)((CREATESTRUCTW*)m->lParam)->lpCreateParams);
+ break;
+
+ case MYWM_NOTIFYICON:
+ {
+ QPoint gpos = QCursor::pos();
+
+ switch (m->lParam) {
+ case WM_LBUTTONUP:
+ if (ignoreNextMouseRelease)
+ ignoreNextMouseRelease = false;
+ else
+ emit q->activated(QSystemTrayIcon::Trigger);
+ break;
+
+ case WM_LBUTTONDBLCLK:
+ ignoreNextMouseRelease = true; // Since DBLCLICK Generates a second mouse
+ // release we must ignore it
+ emit q->activated(QSystemTrayIcon::DoubleClick);
+ break;
+
+ case WM_RBUTTONUP:
+ if (q->contextMenu()) {
+ q->contextMenu()->popup(gpos);
+
+ // We must ensure that the popup menu doesn't show up behind the task bar.
+ QRect desktopRect = qApp->desktop()->availableGeometry();
+ int maxY = desktopRect.y() + desktopRect.height() - q->contextMenu()->height();
+ if (gpos.y() > maxY) {
+ gpos.ry() = maxY;
+ q->contextMenu()->move(gpos);
+ }
+ }
+ emit q->activated(QSystemTrayIcon::Context);
+ break;
+
+ case WM_MBUTTONUP:
+ emit q->activated(QSystemTrayIcon::MiddleClick);
+ break;
+
+ default:
+ break;
+ }
+ break;
+ }
+ default:
+ return QWidget::winEvent(m, result);
+ }
+ return 0;
+}
+
+void QSystemTrayIconPrivate::install_sys()
+{
+ Q_Q(QSystemTrayIcon);
+ if (!sys) {
+ sys = new QSystemTrayIconSys(q);
+ sys->createIcon();
+ sys->trayMessage(NIM_ADD);
+ }
+}
+
+void QSystemTrayIconPrivate::showMessage_sys(const QString &title, const QString &message, QSystemTrayIcon::MessageIcon type, int timeOut)
+{
+ if (!sys)
+ return;
+
+ uint uSecs = 0;
+ if ( timeOut < 0)
+ uSecs = 10000; //10 sec default
+ else uSecs = (int)timeOut;
+
+ //message is limited to 255 chars + NULL
+ QString messageString;
+ if (message.isEmpty() && !title.isEmpty())
+ messageString = QLatin1Char(' '); //ensures that the message shows when only title is set
+ else
+ messageString = message.left(255) + QChar();
+
+ //title is limited to 63 chars + NULL
+ QString titleString = title.left(63) + QChar();
+
+ //show QBalloonTip
+ QRect trayRect = sys->findTrayGeometry();
+ QBalloonTip::showBalloon(type, title, message, sys->q, QPoint(trayRect.left(),
+ trayRect.center().y()), uSecs, false);
+}
+
+QRect QSystemTrayIconPrivate::geometry_sys() const
+{
+ return QRect();
+}
+
+void QSystemTrayIconPrivate::remove_sys()
+{
+ if (!sys)
+ return;
+
+ sys->trayMessage(NIM_DELETE);
+ delete sys;
+ sys = 0;
+}
+
+void QSystemTrayIconPrivate::updateIcon_sys()
+{
+ if (!sys)
+ return;
+
+ HICON hIconToDestroy = sys->hIcon;
+
+ sys->createIcon();
+ sys->trayMessage(NIM_MODIFY);
+
+ if (hIconToDestroy)
+ DestroyIcon(hIconToDestroy);
+}
+
+void QSystemTrayIconPrivate::updateMenu_sys()
+{
+
+}
+
+void QSystemTrayIconPrivate::updateToolTip_sys()
+{
+ // Calling sys->trayMessage(NIM_MODIFY) on an existing icon is broken on Windows CE.
+ // So we need to call updateIcon_sys() which creates a new icon handle.
+ updateIcon_sys();
+}
+
+bool QSystemTrayIconPrivate::isSystemTrayAvailable_sys()
+{
+ return true;
+}
+
+bool QSystemTrayIconPrivate::supportsMessages_sys()
+{
+ return true;
+}
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/widgets/util/qsystemtrayicon_x11.cpp b/src/widgets/util/qsystemtrayicon_x11.cpp
new file mode 100644
index 0000000000..86c27499b4
--- /dev/null
+++ b/src/widgets/util/qsystemtrayicon_x11.cpp
@@ -0,0 +1,401 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "private/qt_x11_p.h"
+#include "qlabel.h"
+#include "qx11info_x11.h"
+#include "qpainter.h"
+#include "qpixmap.h"
+#include "qbitmap.h"
+#include "qevent.h"
+#include "qapplication.h"
+#include "qlist.h"
+#include "qmenu.h"
+#include "qtimer.h"
+#include "qsystemtrayicon_p.h"
+#include "qpaintengine.h"
+
+#ifndef QT_NO_SYSTEMTRAYICON
+QT_BEGIN_NAMESPACE
+
+Window QSystemTrayIconSys::sysTrayWindow = XNone;
+QList<QSystemTrayIconSys *> QSystemTrayIconSys::trayIcons;
+QCoreApplication::EventFilter QSystemTrayIconSys::oldEventFilter = 0;
+Atom QSystemTrayIconSys::sysTraySelection = XNone;
+XVisualInfo QSystemTrayIconSys::sysTrayVisual = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+// Locate the system tray
+Window QSystemTrayIconSys::locateSystemTray()
+{
+ Display *display = QX11Info::display();
+ if (sysTraySelection == XNone) {
+ int screen = QX11Info::appScreen();
+ QString net_sys_tray = QString::fromLatin1("_NET_SYSTEM_TRAY_S%1").arg(screen);
+ sysTraySelection = XInternAtom(display, net_sys_tray.toLatin1(), False);
+ }
+
+ return XGetSelectionOwner(QX11Info::display(), sysTraySelection);
+}
+
+XVisualInfo* QSystemTrayIconSys::getSysTrayVisualInfo()
+{
+ Display *display = QX11Info::display();
+
+ if (!sysTrayVisual.visual) {
+ Window win = locateSystemTray();
+ if (win != XNone) {
+ Atom actual_type;
+ int actual_format;
+ ulong nitems, bytes_remaining;
+ uchar *data = 0;
+ int result = XGetWindowProperty(display, win, ATOM(_NET_SYSTEM_TRAY_VISUAL), 0, 1,
+ False, XA_VISUALID, &actual_type,
+ &actual_format, &nitems, &bytes_remaining, &data);
+ VisualID vid = 0;
+ if (result == Success && data && actual_type == XA_VISUALID && actual_format == 32 &&
+ nitems == 1 && bytes_remaining == 0)
+ vid = *(VisualID*)data;
+ if (data)
+ XFree(data);
+ if (vid == 0)
+ return 0;
+
+ uint mask = VisualIDMask;
+ XVisualInfo *vi, rvi;
+ int count;
+ rvi.visualid = vid;
+ vi = XGetVisualInfo(display, mask, &rvi, &count);
+ if (vi) {
+ sysTrayVisual = vi[0];
+ XFree((char*)vi);
+ }
+ if (sysTrayVisual.depth != 32)
+ memset(&sysTrayVisual, 0, sizeof(sysTrayVisual));
+ }
+ }
+
+ return sysTrayVisual.visual ? &sysTrayVisual : 0;
+}
+
+bool QSystemTrayIconSys::sysTrayTracker(void *message, long *result)
+{
+ bool retval = false;
+ if (QSystemTrayIconSys::oldEventFilter)
+ retval = QSystemTrayIconSys::oldEventFilter(message, result);
+
+ if (trayIcons.isEmpty())
+ return retval;
+
+ Display *display = QX11Info::display();
+ XEvent *ev = (XEvent *)message;
+ if (ev->type == DestroyNotify && ev->xany.window == sysTrayWindow) {
+ sysTrayWindow = locateSystemTray();
+ memset(&sysTrayVisual, 0, sizeof(sysTrayVisual));
+ for (int i = 0; i < trayIcons.count(); i++) {
+ if (sysTrayWindow == XNone) {
+ QBalloonTip::hideBalloon();
+ trayIcons[i]->hide(); // still no luck
+ trayIcons[i]->destroy();
+ trayIcons[i]->create();
+ } else
+ trayIcons[i]->addToTray(); // add it to the new tray
+ }
+ retval = true;
+ } else if (ev->type == ClientMessage && sysTrayWindow == XNone) {
+ static Atom manager_atom = XInternAtom(display, "MANAGER", False);
+ XClientMessageEvent *cm = (XClientMessageEvent *)message;
+ if ((cm->message_type == manager_atom) && ((Atom)cm->data.l[1] == sysTraySelection)) {
+ sysTrayWindow = cm->data.l[2];
+ memset(&sysTrayVisual, 0, sizeof(sysTrayVisual));
+ XSelectInput(display, sysTrayWindow, StructureNotifyMask);
+ for (int i = 0; i < trayIcons.count(); i++) {
+ trayIcons[i]->addToTray();
+ }
+ retval = true;
+ }
+ } else if (ev->type == PropertyNotify && ev->xproperty.atom == ATOM(_NET_SYSTEM_TRAY_VISUAL) &&
+ ev->xproperty.window == sysTrayWindow) {
+ memset(&sysTrayVisual, 0, sizeof(sysTrayVisual));
+ for (int i = 0; i < trayIcons.count(); i++) {
+ trayIcons[i]->addToTray();
+ }
+ }
+
+ return retval;
+}
+
+QSystemTrayIconSys::QSystemTrayIconSys(QSystemTrayIcon *q)
+ : QWidget(0, Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint),
+ q(q), colormap(0)
+{
+ setAttribute(Qt::WA_AlwaysShowToolTips);
+ setAttribute(Qt::WA_QuitOnClose, false);
+ setAttribute(Qt::WA_NoSystemBackground, true);
+ setAttribute(Qt::WA_PaintOnScreen);
+
+ static bool eventFilterAdded = false;
+ Display *display = QX11Info::display();
+ if (!eventFilterAdded) {
+ oldEventFilter = qApp->setEventFilter(sysTrayTracker);
+ eventFilterAdded = true;
+ Window root = QX11Info::appRootWindow();
+ XWindowAttributes attr;
+ XGetWindowAttributes(display, root, &attr);
+ if ((attr.your_event_mask & StructureNotifyMask) != StructureNotifyMask) {
+ (void) QApplication::desktop(); // lame trick to ensure our event mask is not overridden
+ XSelectInput(display, root, attr.your_event_mask | StructureNotifyMask); // for MANAGER selection
+ }
+ }
+ if (trayIcons.isEmpty()) {
+ sysTrayWindow = locateSystemTray();
+ if (sysTrayWindow != XNone)
+ XSelectInput(display, sysTrayWindow, StructureNotifyMask); // track tray events
+ }
+ trayIcons.append(this);
+ setMouseTracking(true);
+#ifndef QT_NO_TOOLTIP
+ setToolTip(q->toolTip());
+#endif
+ if (sysTrayWindow != XNone)
+ addToTray();
+}
+
+QSystemTrayIconSys::~QSystemTrayIconSys()
+{
+ trayIcons.removeAt(trayIcons.indexOf(this));
+ Display *display = QX11Info::display();
+ if (trayIcons.isEmpty()) {
+ if (sysTrayWindow == XNone)
+ return;
+ if (display)
+ XSelectInput(display, sysTrayWindow, 0); // stop tracking the tray
+ sysTrayWindow = XNone;
+ }
+ if (colormap)
+ XFreeColormap(display, colormap);
+}
+
+void QSystemTrayIconSys::addToTray()
+{
+ Q_ASSERT(sysTrayWindow != XNone);
+ Display *display = QX11Info::display();
+
+ XVisualInfo *vi = getSysTrayVisualInfo();
+ if (vi && vi->visual) {
+ Window root = RootWindow(display, vi->screen);
+ Window p = root;
+ if (QWidget *pw = parentWidget())
+ p = pw->effectiveWinId();
+ colormap = XCreateColormap(display, root, vi->visual, AllocNone);
+ XSetWindowAttributes wsa;
+ wsa.background_pixmap = 0;
+ wsa.colormap = colormap;
+ wsa.background_pixel = 0;
+ wsa.border_pixel = 0;
+ Window wid = XCreateWindow(display, p, -1, -1, 1, 1,
+ 0, vi->depth, InputOutput, vi->visual,
+ CWBackPixmap|CWBackPixel|CWBorderPixel|CWColormap, &wsa);
+ create(wid);
+ } else {
+ XSetWindowBackgroundPixmap(display, winId(), ParentRelative);
+ }
+
+ // GNOME, NET WM Specification
+ static Atom netwm_tray_atom = XInternAtom(display, "_NET_SYSTEM_TRAY_OPCODE", False);
+ long l[5] = { CurrentTime, SYSTEM_TRAY_REQUEST_DOCK, winId(), 0, 0 };
+ XEvent ev;
+ memset(&ev, 0, sizeof(ev));
+ ev.xclient.type = ClientMessage;
+ ev.xclient.window = sysTrayWindow;
+ ev.xclient.message_type = netwm_tray_atom;
+ ev.xclient.format = 32;
+ memcpy((char *)&ev.xclient.data, (const char *) l, sizeof(l));
+ XSendEvent(display, sysTrayWindow, False, 0, &ev);
+ setMinimumSize(22, 22); // required at least on GNOME
+}
+
+void QSystemTrayIconSys::updateIcon()
+{
+ update();
+}
+
+void QSystemTrayIconSys::resizeEvent(QResizeEvent *re)
+{
+ QWidget::resizeEvent(re);
+ updateIcon();
+}
+
+void QSystemTrayIconSys::paintEvent(QPaintEvent*)
+{
+ QPainter p(this);
+ if (!getSysTrayVisualInfo()) {
+ const QRegion oldSystemClip = p.paintEngine()->systemClip();
+ const QRect clearedRect = oldSystemClip.boundingRect();
+ XClearArea(QX11Info::display(), winId(), clearedRect.x(), clearedRect.y(),
+ clearedRect.width(), clearedRect.height(), False);
+ QPaintEngine *pe = p.paintEngine();
+ pe->setSystemClip(clearedRect);
+ q->icon().paint(&p, rect());
+ pe->setSystemClip(oldSystemClip);
+ } else {
+ p.setCompositionMode(QPainter::CompositionMode_Source);
+ p.fillRect(rect(), Qt::transparent);
+ p.setCompositionMode(QPainter::CompositionMode_SourceOver);
+ q->icon().paint(&p, rect());
+ }
+}
+
+void QSystemTrayIconSys::mousePressEvent(QMouseEvent *ev)
+{
+ QPoint globalPos = ev->globalPos();
+ if (ev->button() == Qt::RightButton && q->contextMenu())
+ q->contextMenu()->popup(globalPos);
+
+ if (QBalloonTip::isBalloonVisible()) {
+ emit q->messageClicked();
+ QBalloonTip::hideBalloon();
+ }
+
+ if (ev->button() == Qt::LeftButton)
+ emit q->activated(QSystemTrayIcon::Trigger);
+ else if (ev->button() == Qt::RightButton)
+ emit q->activated(QSystemTrayIcon::Context);
+ else if (ev->button() == Qt::MidButton)
+ emit q->activated(QSystemTrayIcon::MiddleClick);
+}
+
+void QSystemTrayIconSys::mouseDoubleClickEvent(QMouseEvent *ev)
+{
+ if (ev->button() == Qt::LeftButton)
+ emit q->activated(QSystemTrayIcon::DoubleClick);
+}
+
+#ifndef QT_NO_WHEELEVENT
+void QSystemTrayIconSys::wheelEvent(QWheelEvent *e)
+{
+ QApplication::sendEvent(q, e);
+}
+#endif
+
+bool QSystemTrayIconSys::event(QEvent *e)
+{
+ if (e->type() == QEvent::ToolTip) {
+ return QApplication::sendEvent(q, e);
+ }
+ return QWidget::event(e);
+}
+
+bool QSystemTrayIconSys::x11Event(XEvent *event)
+{
+ if (event->type == ReparentNotify)
+ show();
+ return QWidget::x11Event(event);
+}
+
+////////////////////////////////////////////////////////////////////////////
+void QSystemTrayIconPrivate::install_sys()
+{
+ Q_Q(QSystemTrayIcon);
+ if (!sys)
+ sys = new QSystemTrayIconSys(q);
+}
+
+QRect QSystemTrayIconPrivate::geometry_sys() const
+{
+ if (!sys)
+ return QRect();
+ return QRect(sys->mapToGlobal(QPoint(0, 0)), sys->size());
+}
+
+void QSystemTrayIconPrivate::remove_sys()
+{
+ if (!sys)
+ return;
+ QBalloonTip::hideBalloon();
+ sys->hide(); // this should do the trick, but...
+ delete sys; // wm may resize system tray only for DestroyEvents
+ sys = 0;
+}
+
+void QSystemTrayIconPrivate::updateIcon_sys()
+{
+ if (!sys)
+ return;
+ sys->updateIcon();
+}
+
+void QSystemTrayIconPrivate::updateMenu_sys()
+{
+
+}
+
+void QSystemTrayIconPrivate::updateToolTip_sys()
+{
+ if (!sys)
+ return;
+#ifndef QT_NO_TOOLTIP
+ sys->setToolTip(toolTip);
+#endif
+}
+
+bool QSystemTrayIconPrivate::isSystemTrayAvailable_sys()
+{
+ return QSystemTrayIconSys::locateSystemTray() != XNone;
+}
+
+bool QSystemTrayIconPrivate::supportsMessages_sys()
+{
+ return true;
+}
+
+void QSystemTrayIconPrivate::showMessage_sys(const QString &message, const QString &title,
+ QSystemTrayIcon::MessageIcon icon, int msecs)
+{
+ if (!sys)
+ return;
+ QPoint g = sys->mapToGlobal(QPoint(0, 0));
+ QBalloonTip::showBalloon(icon, message, title, sys->q,
+ QPoint(g.x() + sys->width()/2, g.y() + sys->height()/2),
+ msecs);
+}
+
+QT_END_NAMESPACE
+#endif //QT_NO_SYSTEMTRAYICON
diff --git a/src/widgets/util/qundogroup.cpp b/src/widgets/util/qundogroup.cpp
new file mode 100644
index 0000000000..4f83e3a815
--- /dev/null
+++ b/src/widgets/util/qundogroup.cpp
@@ -0,0 +1,505 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qundogroup.h"
+#include "qundostack.h"
+#include "qundostack_p.h"
+
+#ifndef QT_NO_UNDOGROUP
+
+QT_BEGIN_NAMESPACE
+
+class QUndoGroupPrivate : public QObjectPrivate
+{
+ Q_DECLARE_PUBLIC(QUndoGroup)
+public:
+ QUndoGroupPrivate() : active(0) {}
+
+ QUndoStack *active;
+ QList<QUndoStack*> stack_list;
+};
+
+/*!
+ \class QUndoGroup
+ \brief The QUndoGroup class is a group of QUndoStack objects.
+ \since 4.2
+
+ For an overview of the Qt's undo framework, see the
+ \link qundo.html overview\endlink.
+
+ An application often has multiple undo stacks, one for each opened document. At the
+ same time, an application usually has one undo action and one redo action, which
+ triggers undo or redo in the active document.
+
+ QUndoGroup is a group of QUndoStack objects, one of which may be active. It has
+ an undo() and redo() slot, which calls QUndoStack::undo() and QUndoStack::redo()
+ for the active stack. It also has the functions createUndoAction() and createRedoAction().
+ The actions returned by these functions behave in the same way as those returned by
+ QUndoStack::createUndoAction() and QUndoStack::createRedoAction() of the active
+ stack.
+
+ Stacks are added to a group with addStack() and removed with removeStack(). A stack
+ is implicitly added to a group when it is created with the group as its parent
+ QObject.
+
+ It is the programmer's responsibility to specify which stack is active by
+ calling QUndoStack::setActive(), usually when the associated document window receives focus.
+ The active stack may also be set with setActiveStack(), and is returned by activeStack().
+
+ When a stack is added to a group using addStack(), the group does not take ownership
+ of the stack. This means the stack has to be deleted separately from the group. When
+ a stack is deleted, it is automatically removed from a group. A stack may belong to
+ only one group. Adding it to another group will cause it to be removed from the previous
+ group.
+
+ A QUndoGroup is also useful in conjunction with QUndoView. If a QUndoView is
+ set to watch a group using QUndoView::setGroup(), it will update itself to display
+ the active stack.
+*/
+
+/*!
+ Creates an empty QUndoGroup object with parent \a parent.
+
+ \sa addStack()
+*/
+
+QUndoGroup::QUndoGroup(QObject *parent)
+ : QObject(*new QUndoGroupPrivate(), parent)
+{
+}
+
+/*!
+ Destroys the QUndoGroup.
+*/
+QUndoGroup::~QUndoGroup()
+{
+ // Ensure all QUndoStacks no longer refer to this group.
+ Q_D(QUndoGroup);
+ QList<QUndoStack *>::iterator it = d->stack_list.begin();
+ QList<QUndoStack *>::iterator end = d->stack_list.end();
+ while (it != end) {
+ (*it)->d_func()->group = 0;
+ ++it;
+ }
+}
+
+/*!
+ Adds \a stack to this group. The group does not take ownership of the stack. Another
+ way of adding a stack to a group is by specifying the group as the stack's parent
+ QObject in QUndoStack::QUndoStack(). In this case, the stack is deleted when the
+ group is deleted, in the usual manner of QObjects.
+
+ \sa removeStack() stacks() QUndoStack::QUndoStack()
+*/
+
+void QUndoGroup::addStack(QUndoStack *stack)
+{
+ Q_D(QUndoGroup);
+
+ if (d->stack_list.contains(stack))
+ return;
+ d->stack_list.append(stack);
+
+ if (QUndoGroup *other = stack->d_func()->group)
+ other->removeStack(stack);
+ stack->d_func()->group = this;
+}
+
+/*!
+ Removes \a stack from this group. If the stack was the active stack in the group,
+ the active stack becomes 0.
+
+ \sa addStack() stacks() QUndoStack::~QUndoStack()
+*/
+
+void QUndoGroup::removeStack(QUndoStack *stack)
+{
+ Q_D(QUndoGroup);
+
+ if (d->stack_list.removeAll(stack) == 0)
+ return;
+ if (stack == d->active)
+ setActiveStack(0);
+ stack->d_func()->group = 0;
+}
+
+/*!
+ Returns a list of stacks in this group.
+
+ \sa addStack() removeStack()
+*/
+
+QList<QUndoStack*> QUndoGroup::stacks() const
+{
+ Q_D(const QUndoGroup);
+ return d->stack_list;
+}
+
+/*!
+ Sets the active stack of this group to \a stack.
+
+ If the stack is not a member of this group, this function does nothing.
+
+ Synonymous with calling QUndoStack::setActive() on \a stack.
+
+ The actions returned by createUndoAction() and createRedoAction() will now behave
+ in the same way as those returned by \a stack's QUndoStack::createUndoAction()
+ and QUndoStack::createRedoAction().
+
+ \sa QUndoStack::setActive() activeStack()
+*/
+
+void QUndoGroup::setActiveStack(QUndoStack *stack)
+{
+ Q_D(QUndoGroup);
+ if (d->active == stack)
+ return;
+
+ if (d->active != 0) {
+ disconnect(d->active, SIGNAL(canUndoChanged(bool)),
+ this, SIGNAL(canUndoChanged(bool)));
+ disconnect(d->active, SIGNAL(undoTextChanged(QString)),
+ this, SIGNAL(undoTextChanged(QString)));
+ disconnect(d->active, SIGNAL(canRedoChanged(bool)),
+ this, SIGNAL(canRedoChanged(bool)));
+ disconnect(d->active, SIGNAL(redoTextChanged(QString)),
+ this, SIGNAL(redoTextChanged(QString)));
+ disconnect(d->active, SIGNAL(indexChanged(int)),
+ this, SIGNAL(indexChanged(int)));
+ disconnect(d->active, SIGNAL(cleanChanged(bool)),
+ this, SIGNAL(cleanChanged(bool)));
+ }
+
+ d->active = stack;
+
+ if (d->active == 0) {
+ emit canUndoChanged(false);
+ emit undoTextChanged(QString());
+ emit canRedoChanged(false);
+ emit redoTextChanged(QString());
+ emit cleanChanged(true);
+ emit indexChanged(0);
+ } else {
+ connect(d->active, SIGNAL(canUndoChanged(bool)),
+ this, SIGNAL(canUndoChanged(bool)));
+ connect(d->active, SIGNAL(undoTextChanged(QString)),
+ this, SIGNAL(undoTextChanged(QString)));
+ connect(d->active, SIGNAL(canRedoChanged(bool)),
+ this, SIGNAL(canRedoChanged(bool)));
+ connect(d->active, SIGNAL(redoTextChanged(QString)),
+ this, SIGNAL(redoTextChanged(QString)));
+ connect(d->active, SIGNAL(indexChanged(int)),
+ this, SIGNAL(indexChanged(int)));
+ connect(d->active, SIGNAL(cleanChanged(bool)),
+ this, SIGNAL(cleanChanged(bool)));
+ emit canUndoChanged(d->active->canUndo());
+ emit undoTextChanged(d->active->undoText());
+ emit canRedoChanged(d->active->canRedo());
+ emit redoTextChanged(d->active->redoText());
+ emit cleanChanged(d->active->isClean());
+ emit indexChanged(d->active->index());
+ }
+
+ emit activeStackChanged(d->active);
+}
+
+/*!
+ Returns the active stack of this group.
+
+ If none of the stacks are active, or if the group is empty, this function
+ returns 0.
+
+ \sa setActiveStack() QUndoStack::setActive()
+*/
+
+QUndoStack *QUndoGroup::activeStack() const
+{
+ Q_D(const QUndoGroup);
+ return d->active;
+}
+
+/*!
+ Calls QUndoStack::undo() on the active stack.
+
+ If none of the stacks are active, or if the group is empty, this function
+ does nothing.
+
+ \sa redo() canUndo() setActiveStack()
+*/
+
+void QUndoGroup::undo()
+{
+ Q_D(QUndoGroup);
+ if (d->active != 0)
+ d->active->undo();
+}
+
+/*!
+ Calls QUndoStack::redo() on the active stack.
+
+ If none of the stacks are active, or if the group is empty, this function
+ does nothing.
+
+ \sa undo() canRedo() setActiveStack()
+*/
+
+
+void QUndoGroup::redo()
+{
+ Q_D(QUndoGroup);
+ if (d->active != 0)
+ d->active->redo();
+}
+
+/*!
+ Returns the value of the active stack's QUndoStack::canUndo().
+
+ If none of the stacks are active, or if the group is empty, this function
+ returns false.
+
+ \sa canRedo() setActiveStack()
+*/
+
+bool QUndoGroup::canUndo() const
+{
+ Q_D(const QUndoGroup);
+ return d->active != 0 && d->active->canUndo();
+}
+
+/*!
+ Returns the value of the active stack's QUndoStack::canRedo().
+
+ If none of the stacks are active, or if the group is empty, this function
+ returns false.
+
+ \sa canUndo() setActiveStack()
+*/
+
+bool QUndoGroup::canRedo() const
+{
+ Q_D(const QUndoGroup);
+ return d->active != 0 && d->active->canRedo();
+}
+
+/*!
+ Returns the value of the active stack's QUndoStack::undoText().
+
+ If none of the stacks are active, or if the group is empty, this function
+ returns an empty string.
+
+ \sa redoText() setActiveStack()
+*/
+
+QString QUndoGroup::undoText() const
+{
+ Q_D(const QUndoGroup);
+ return d->active == 0 ? QString() : d->active->undoText();
+}
+
+/*!
+ Returns the value of the active stack's QUndoStack::redoText().
+
+ If none of the stacks are active, or if the group is empty, this function
+ returns an empty string.
+
+ \sa undoText() setActiveStack()
+*/
+
+QString QUndoGroup::redoText() const
+{
+ Q_D(const QUndoGroup);
+ return d->active == 0 ? QString() : d->active->redoText();
+}
+
+/*!
+ Returns the value of the active stack's QUndoStack::isClean().
+
+ If none of the stacks are active, or if the group is empty, this function
+ returns true.
+
+ \sa setActiveStack()
+*/
+
+bool QUndoGroup::isClean() const
+{
+ Q_D(const QUndoGroup);
+ return d->active == 0 || d->active->isClean();
+}
+
+#ifndef QT_NO_ACTION
+
+/*!
+ Creates an undo QAction object with parent \a parent.
+
+ Triggering this action will cause a call to QUndoStack::undo() on the active stack.
+ The text of this action will always be the text of the command which will be undone
+ in the next call to undo(), prefixed by \a prefix. If there is no command available
+ for undo, if the group is empty or if none of the stacks are active, this action will
+ be disabled.
+
+ If \a prefix is empty, the default template "Undo %1" is used instead of prefix.
+ Before Qt 4.8, the prefix "Undo" was used by default.
+
+ \sa createRedoAction() canUndo() QUndoCommand::text()
+*/
+
+QAction *QUndoGroup::createUndoAction(QObject *parent, const QString &prefix) const
+{
+ QUndoAction *result = new QUndoAction(prefix, parent);
+ if (prefix.isEmpty())
+ result->setTextFormat(tr("Undo %1"), tr("Undo", "Default text for undo action"));
+
+ result->setEnabled(canUndo());
+ result->setPrefixedText(undoText());
+ connect(this, SIGNAL(canUndoChanged(bool)),
+ result, SLOT(setEnabled(bool)));
+ connect(this, SIGNAL(undoTextChanged(QString)),
+ result, SLOT(setPrefixedText(QString)));
+ connect(result, SIGNAL(triggered()), this, SLOT(undo()));
+ return result;
+}
+
+/*!
+ Creates an redo QAction object with parent \a parent.
+
+ Triggering this action will cause a call to QUndoStack::redo() on the active stack.
+ The text of this action will always be the text of the command which will be redone
+ in the next call to redo(), prefixed by \a prefix. If there is no command available
+ for redo, if the group is empty or if none of the stacks are active, this action will
+ be disabled.
+
+ If \a prefix is empty, the default template "Redo %1" is used instead of prefix.
+ Before Qt 4.8, the prefix "Redo" was used by default.
+
+ \sa createUndoAction() canRedo() QUndoCommand::text()
+*/
+
+QAction *QUndoGroup::createRedoAction(QObject *parent, const QString &prefix) const
+{
+ QUndoAction *result = new QUndoAction(prefix, parent);
+ if (prefix.isEmpty())
+ result->setTextFormat(tr("Redo %1"), tr("Redo", "Default text for redo action"));
+
+ result->setEnabled(canRedo());
+ result->setPrefixedText(redoText());
+ connect(this, SIGNAL(canRedoChanged(bool)),
+ result, SLOT(setEnabled(bool)));
+ connect(this, SIGNAL(redoTextChanged(QString)),
+ result, SLOT(setPrefixedText(QString)));
+ connect(result, SIGNAL(triggered()), this, SLOT(redo()));
+ return result;
+}
+
+#endif // QT_NO_ACTION
+
+/*! \fn void QUndoGroup::activeStackChanged(QUndoStack *stack)
+
+ This signal is emitted whenever the active stack of the group changes. This can happen
+ when setActiveStack() or QUndoStack::setActive() is called, or when the active stack
+ is removed form the group. \a stack is the new active stack. If no stack is active,
+ \a stack is 0.
+
+ \sa setActiveStack() QUndoStack::setActive()
+*/
+
+/*! \fn void QUndoGroup::indexChanged(int idx)
+
+ This signal is emitted whenever the active stack emits QUndoStack::indexChanged()
+ or the active stack changes.
+
+ \a idx is the new current index, or 0 if the active stack is 0.
+
+ \sa QUndoStack::indexChanged() setActiveStack()
+*/
+
+/*! \fn void QUndoGroup::cleanChanged(bool clean)
+
+ This signal is emitted whenever the active stack emits QUndoStack::cleanChanged()
+ or the active stack changes.
+
+ \a clean is the new state, or true if the active stack is 0.
+
+ \sa QUndoStack::cleanChanged() setActiveStack()
+*/
+
+/*! \fn void QUndoGroup::canUndoChanged(bool canUndo)
+
+ This signal is emitted whenever the active stack emits QUndoStack::canUndoChanged()
+ or the active stack changes.
+
+ \a canUndo is the new state, or false if the active stack is 0.
+
+ \sa QUndoStack::canUndoChanged() setActiveStack()
+*/
+
+/*! \fn void QUndoGroup::canRedoChanged(bool canRedo)
+
+ This signal is emitted whenever the active stack emits QUndoStack::canRedoChanged()
+ or the active stack changes.
+
+ \a canRedo is the new state, or false if the active stack is 0.
+
+ \sa QUndoStack::canRedoChanged() setActiveStack()
+*/
+
+/*! \fn void QUndoGroup::undoTextChanged(const QString &undoText)
+
+ This signal is emitted whenever the active stack emits QUndoStack::undoTextChanged()
+ or the active stack changes.
+
+ \a undoText is the new state, or an empty string if the active stack is 0.
+
+ \sa QUndoStack::undoTextChanged() setActiveStack()
+*/
+
+/*! \fn void QUndoGroup::redoTextChanged(const QString &redoText)
+
+ This signal is emitted whenever the active stack emits QUndoStack::redoTextChanged()
+ or the active stack changes.
+
+ \a redoText is the new state, or an empty string if the active stack is 0.
+
+ \sa QUndoStack::redoTextChanged() setActiveStack()
+*/
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_UNDOGROUP
diff --git a/src/widgets/util/qundogroup.h b/src/widgets/util/qundogroup.h
new file mode 100644
index 0000000000..ce2dac2503
--- /dev/null
+++ b/src/widgets/util/qundogroup.h
@@ -0,0 +1,110 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QUNDOGROUP_H
+#define QUNDOGROUP_H
+
+#include <QtCore/qobject.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+class QUndoGroupPrivate;
+class QUndoStack;
+class QAction;
+
+QT_MODULE(Gui)
+
+#ifndef QT_NO_UNDOGROUP
+
+class Q_WIDGETS_EXPORT QUndoGroup : public QObject
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(QUndoGroup)
+
+public:
+ explicit QUndoGroup(QObject *parent = 0);
+ ~QUndoGroup();
+
+ void addStack(QUndoStack *stack);
+ void removeStack(QUndoStack *stack);
+ QList<QUndoStack*> stacks() const;
+ QUndoStack *activeStack() const;
+
+#ifndef QT_NO_ACTION
+ QAction *createUndoAction(QObject *parent,
+ const QString &prefix = QString()) const;
+ QAction *createRedoAction(QObject *parent,
+ const QString &prefix = QString()) const;
+#endif // QT_NO_ACTION
+ bool canUndo() const;
+ bool canRedo() const;
+ QString undoText() const;
+ QString redoText() const;
+ bool isClean() const;
+
+public Q_SLOTS:
+ void undo();
+ void redo();
+ void setActiveStack(QUndoStack *stack);
+
+Q_SIGNALS:
+ void activeStackChanged(QUndoStack *stack);
+ void indexChanged(int idx);
+ void cleanChanged(bool clean);
+ void canUndoChanged(bool canUndo);
+ void canRedoChanged(bool canRedo);
+ void undoTextChanged(const QString &undoText);
+ void redoTextChanged(const QString &redoText);
+
+private:
+ Q_DISABLE_COPY(QUndoGroup)
+};
+
+#endif // QT_NO_UNDOGROUP
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QUNDOGROUP_H
diff --git a/src/widgets/util/qundostack.cpp b/src/widgets/util/qundostack.cpp
new file mode 100644
index 0000000000..59e73e6ad2
--- /dev/null
+++ b/src/widgets/util/qundostack.cpp
@@ -0,0 +1,1175 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore/qdebug.h>
+#include "qundostack.h"
+#include "qundogroup.h"
+#include "qundostack_p.h"
+
+#ifndef QT_NO_UNDOCOMMAND
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QUndoCommand
+ \brief The QUndoCommand class is the base class of all commands stored on a QUndoStack.
+ \since 4.2
+
+ For an overview of Qt's Undo Framework, see the
+ \l{Overview of Qt's Undo Framework}{overview document}.
+
+ A QUndoCommand represents a single editing action on a document; for example,
+ inserting or deleting a block of text in a text editor. QUndoCommand can apply
+ a change to the document with redo() and undo the change with undo(). The
+ implementations for these functions must be provided in a derived class.
+
+ \snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 0
+
+ A QUndoCommand has an associated text(). This is a short string
+ describing what the command does. It is used to update the text
+ properties of the stack's undo and redo actions; see
+ QUndoStack::createUndoAction() and QUndoStack::createRedoAction().
+
+ QUndoCommand objects are owned by the stack they were pushed on.
+ QUndoStack deletes a command if it has been undone and a new command is pushed. For example:
+
+\snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 1
+
+ In effect, when a command is pushed, it becomes the top-most command
+ on the stack.
+
+ To support command compression, QUndoCommand has an id() and the virtual function
+ mergeWith(). These functions are used by QUndoStack::push().
+
+ To support command macros, a QUndoCommand object can have any number of child
+ commands. Undoing or redoing the parent command will cause the child
+ commands to be undone or redone. A command can be assigned
+ to a parent explicitly in the constructor. In this case, the command
+ will be owned by the parent.
+
+ The parent in this case is usually an empty command, in that it doesn't
+ provide its own implementation of undo() and redo(). Instead, it uses
+ the base implementations of these functions, which simply call undo() or
+ redo() on all its children. The parent should, however, have a meaningful
+ text().
+
+ \snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 2
+
+ Another way to create macros is to use the convenience functions
+ QUndoStack::beginMacro() and QUndoStack::endMacro().
+
+ \sa QUndoStack
+*/
+
+/*!
+ Constructs a QUndoCommand object with the given \a parent and \a text.
+
+ If \a parent is not 0, this command is appended to parent's child list.
+ The parent command then owns this command and will delete it in its
+ destructor.
+
+ \sa ~QUndoCommand()
+*/
+
+QUndoCommand::QUndoCommand(const QString &text, QUndoCommand *parent)
+{
+ d = new QUndoCommandPrivate;
+ if (parent != 0)
+ parent->d->child_list.append(this);
+ setText(text);
+}
+
+/*!
+ Constructs a QUndoCommand object with parent \a parent.
+
+ If \a parent is not 0, this command is appended to parent's child list.
+ The parent command then owns this command and will delete it in its
+ destructor.
+
+ \sa ~QUndoCommand()
+*/
+
+QUndoCommand::QUndoCommand(QUndoCommand *parent)
+{
+ d = new QUndoCommandPrivate;
+ if (parent != 0)
+ parent->d->child_list.append(this);
+}
+
+/*!
+ Destroys the QUndoCommand object and all child commands.
+
+ \sa QUndoCommand()
+*/
+
+QUndoCommand::~QUndoCommand()
+{
+ qDeleteAll(d->child_list);
+ delete d;
+}
+
+/*!
+ Returns the ID of this command.
+
+ A command ID is used in command compression. It must be an integer unique to
+ this command's class, or -1 if the command doesn't support compression.
+
+ If the command supports compression this function must be overridden in the
+ derived class to return the correct ID. The base implementation returns -1.
+
+ QUndoStack::push() will only try to merge two commands if they have the
+ same ID, and the ID is not -1.
+
+ \sa mergeWith(), QUndoStack::push()
+*/
+
+int QUndoCommand::id() const
+{
+ return -1;
+}
+
+/*!
+ Attempts to merge this command with \a command. Returns true on
+ success; otherwise returns false.
+
+ If this function returns true, calling this command's redo() must have the same
+ effect as redoing both this command and \a command.
+ Similarly, calling this command's undo() must have the same effect as undoing
+ \a command and this command.
+
+ QUndoStack will only try to merge two commands if they have the same id, and
+ the id is not -1.
+
+ The default implementation returns false.
+
+ \snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 3
+
+ \sa id() QUndoStack::push()
+*/
+
+bool QUndoCommand::mergeWith(const QUndoCommand *command)
+{
+ Q_UNUSED(command);
+ return false;
+}
+
+/*!
+ Applies a change to the document. This function must be implemented in
+ the derived class. Calling QUndoStack::push(),
+ QUndoStack::undo() or QUndoStack::redo() from this function leads to
+ undefined beahavior.
+
+ The default implementation calls redo() on all child commands.
+
+ \sa undo()
+*/
+
+void QUndoCommand::redo()
+{
+ for (int i = 0; i < d->child_list.size(); ++i)
+ d->child_list.at(i)->redo();
+}
+
+/*!
+ Reverts a change to the document. After undo() is called, the state of
+ the document should be the same as before redo() was called. This function must
+ be implemented in the derived class. Calling QUndoStack::push(),
+ QUndoStack::undo() or QUndoStack::redo() from this function leads to
+ undefined beahavior.
+
+ The default implementation calls undo() on all child commands in reverse order.
+
+ \sa redo()
+*/
+
+void QUndoCommand::undo()
+{
+ for (int i = d->child_list.size() - 1; i >= 0; --i)
+ d->child_list.at(i)->undo();
+}
+
+/*!
+ Returns a short text string describing what this command does; for example,
+ "insert text".
+
+ The text is used for names of items in QUndoView.
+
+ \sa actionText(), setText(), QUndoStack::createUndoAction(), QUndoStack::createRedoAction()
+*/
+
+QString QUndoCommand::text() const
+{
+ return d->text;
+}
+
+/*!
+ \since 4.8
+
+ Returns a short text string describing what this command does; for example,
+ "insert text".
+
+ The text is used when the text properties of the stack's undo and redo
+ actions are updated.
+
+ \sa text(), setText(), QUndoStack::createUndoAction(), QUndoStack::createRedoAction()
+*/
+
+QString QUndoCommand::actionText() const
+{
+ return d->actionText;
+}
+
+/*!
+ Sets the command's text to be the \a text specified.
+
+ The specified text should be a short user-readable string describing what this
+ command does.
+
+ If you need to have two different strings for text() and actionText(), separate
+ them with "\n" and pass into this function. Even if you do not use this feature
+ for English strings during development, you can still let translators use two
+ different strings in order to match specific languages' needs.
+ The described feature and the function actionText() are available since Qt 4.8.
+
+ \sa text() actionText() QUndoStack::createUndoAction() QUndoStack::createRedoAction()
+*/
+
+void QUndoCommand::setText(const QString &text)
+{
+ int cdpos = text.indexOf(QLatin1Char('\n'));
+ if (cdpos > 0) {
+ d->text = text.left(cdpos);
+ d->actionText = text.mid(cdpos + 1);
+ } else {
+ d->text = text;
+ d->actionText = text;
+ }
+}
+
+/*!
+ \since 4.4
+
+ Returns the number of child commands in this command.
+
+ \sa child()
+*/
+
+int QUndoCommand::childCount() const
+{
+ return d->child_list.count();
+}
+
+/*!
+ \since 4.4
+
+ Returns the child command at \a index.
+
+ \sa childCount(), QUndoStack::command()
+*/
+
+const QUndoCommand *QUndoCommand::child(int index) const
+{
+ if (index < 0 || index >= d->child_list.count())
+ return 0;
+ return d->child_list.at(index);
+}
+
+#endif // QT_NO_UNDOCOMMAND
+
+#ifndef QT_NO_UNDOSTACK
+
+/*!
+ \class QUndoStack
+ \brief The QUndoStack class is a stack of QUndoCommand objects.
+ \since 4.2
+
+ For an overview of Qt's Undo Framework, see the
+ \l{Overview of Qt's Undo Framework}{overview document}.
+
+ An undo stack maintains a stack of commands that have been applied to a
+ document.
+
+ New commands are pushed on the stack using push(). Commands can be
+ undone and redone using undo() and redo(), or by triggering the
+ actions returned by createUndoAction() and createRedoAction().
+
+ QUndoStack keeps track of the \a current command. This is the command
+ which will be executed by the next call to redo(). The index of this
+ command is returned by index(). The state of the edited object can be
+ rolled forward or back using setIndex(). If the top-most command on the
+ stack has already been redone, index() is equal to count().
+
+ QUndoStack provides support for undo and redo actions, command
+ compression, command macros, and supports the concept of a
+ \e{clean state}.
+
+ \section1 Undo and Redo Actions
+
+ QUndoStack provides convenient undo and redo QAction objects, which
+ can be inserted into a menu or a toolbar. When commands are undone or
+ redone, QUndoStack updates the text properties of these actions
+ to reflect what change they will trigger. The actions are also disabled
+ when no command is available for undo or redo. These actions
+ are returned by QUndoStack::createUndoAction() and QUndoStack::createRedoAction().
+
+ \section1 Command Compression and Macros
+
+ Command compression is useful when several commands can be compressed
+ into a single command that can be undone and redone in a single operation.
+ For example, when a user types a character in a text editor, a new command
+ is created. This command inserts the character into the document at the
+ cursor position. However, it is more convenient for the user to be able
+ to undo or redo typing of whole words, sentences, or paragraphs.
+ Command compression allows these single-character commands to be merged
+ into a single command which inserts or deletes sections of text.
+ For more information, see QUndoCommand::mergeWith() and push().
+
+ A command macro is a sequence of commands, all of which are undone and
+ redone in one go. Command macros are created by giving a command a list
+ of child commands.
+ Undoing or redoing the parent command will cause the child commands to
+ be undone or redone. Command macros may be created explicitly
+ by specifying a parent in the QUndoCommand constructor, or by using the
+ convenience functions beginMacro() and endMacro().
+
+ Although command compression and macros appear to have the same effect to the
+ user, they often have different uses in an application. Commands that
+ perform small changes to a document may be usefully compressed if there is
+ no need to individually record them, and if only larger changes are relevant
+ to the user.
+ However, for commands that need to be recorded individually, or those that
+ cannot be compressed, it is useful to use macros to provide a more convenient
+ user experience while maintaining a record of each command.
+
+ \section1 Clean State
+
+ QUndoStack supports the concept of a clean state. When the
+ document is saved to disk, the stack can be marked as clean using
+ setClean(). Whenever the stack returns to this state through undoing and
+ redoing commands, it emits the signal cleanChanged(). This signal
+ is also emitted when the stack leaves the clean state. This signal is
+ usually used to enable and disable the save actions in the application,
+ and to update the document's title to reflect that it contains unsaved
+ changes.
+
+ \sa QUndoCommand, QUndoView
+*/
+
+#ifndef QT_NO_ACTION
+
+QUndoAction::QUndoAction(const QString &prefix, QObject *parent)
+ : QAction(parent)
+{
+ m_prefix = prefix;
+}
+
+void QUndoAction::setPrefixedText(const QString &text)
+{
+ if (m_defaultText.isEmpty()) {
+ QString s = m_prefix;
+ if (!m_prefix.isEmpty() && !text.isEmpty())
+ s.append(QLatin1Char(' '));
+ s.append(text);
+ setText(s);
+ } else {
+ if (text.isEmpty())
+ setText(m_defaultText);
+ else
+ setText(m_prefix.arg(text));
+ }
+}
+
+void QUndoAction::setTextFormat(const QString &textFormat, const QString &defaultText)
+{
+ m_prefix = textFormat;
+ m_defaultText = defaultText;
+}
+
+#endif // QT_NO_ACTION
+
+/*! \internal
+ Sets the current index to \a idx, emitting appropriate signals. If \a clean is true,
+ makes \a idx the clean index as well.
+*/
+
+void QUndoStackPrivate::setIndex(int idx, bool clean)
+{
+ Q_Q(QUndoStack);
+
+ bool was_clean = index == clean_index;
+
+ if (idx != index) {
+ index = idx;
+ emit q->indexChanged(index);
+ emit q->canUndoChanged(q->canUndo());
+ emit q->undoTextChanged(q->undoText());
+ emit q->canRedoChanged(q->canRedo());
+ emit q->redoTextChanged(q->redoText());
+ }
+
+ if (clean)
+ clean_index = index;
+
+ bool is_clean = index == clean_index;
+ if (is_clean != was_clean)
+ emit q->cleanChanged(is_clean);
+}
+
+/*! \internal
+ If the number of commands on the stack exceedes the undo limit, deletes commands from
+ the bottom of the stack.
+
+ Returns true if commands were deleted.
+*/
+
+bool QUndoStackPrivate::checkUndoLimit()
+{
+ if (undo_limit <= 0 || !macro_stack.isEmpty() || undo_limit >= command_list.count())
+ return false;
+
+ int del_count = command_list.count() - undo_limit;
+
+ for (int i = 0; i < del_count; ++i)
+ delete command_list.takeFirst();
+
+ index -= del_count;
+ if (clean_index != -1) {
+ if (clean_index < del_count)
+ clean_index = -1; // we've deleted the clean command
+ else
+ clean_index -= del_count;
+ }
+
+ return true;
+}
+
+/*!
+ Constructs an empty undo stack with the parent \a parent. The
+ stack will initially be in the clean state. If \a parent is a
+ QUndoGroup object, the stack is automatically added to the group.
+
+ \sa push()
+*/
+
+QUndoStack::QUndoStack(QObject *parent)
+ : QObject(*(new QUndoStackPrivate), parent)
+{
+#ifndef QT_NO_UNDOGROUP
+ if (QUndoGroup *group = qobject_cast<QUndoGroup*>(parent))
+ group->addStack(this);
+#endif
+}
+
+/*!
+ Destroys the undo stack, deleting any commands that are on it. If the
+ stack is in a QUndoGroup, the stack is automatically removed from the group.
+
+ \sa QUndoStack()
+*/
+
+QUndoStack::~QUndoStack()
+{
+#ifndef QT_NO_UNDOGROUP
+ Q_D(QUndoStack);
+ if (d->group != 0)
+ d->group->removeStack(this);
+#endif
+ clear();
+}
+
+/*!
+ Clears the command stack by deleting all commands on it, and returns the stack
+ to the clean state.
+
+ Commands are not undone or redone; the state of the edited object remains
+ unchanged.
+
+ This function is usually used when the contents of the document are
+ abandoned.
+
+ \sa QUndoStack()
+*/
+
+void QUndoStack::clear()
+{
+ Q_D(QUndoStack);
+
+ if (d->command_list.isEmpty())
+ return;
+
+ bool was_clean = isClean();
+
+ d->macro_stack.clear();
+ qDeleteAll(d->command_list);
+ d->command_list.clear();
+
+ d->index = 0;
+ d->clean_index = 0;
+
+ emit indexChanged(0);
+ emit canUndoChanged(false);
+ emit undoTextChanged(QString());
+ emit canRedoChanged(false);
+ emit redoTextChanged(QString());
+
+ if (!was_clean)
+ emit cleanChanged(true);
+}
+
+/*!
+ Pushes \a cmd on the stack or merges it with the most recently executed command.
+ In either case, executes \a cmd by calling its redo() function.
+
+ If \a cmd's id is not -1, and if the id is the same as that of the
+ most recently executed command, QUndoStack will attempt to merge the two
+ commands by calling QUndoCommand::mergeWith() on the most recently executed
+ command. If QUndoCommand::mergeWith() returns true, \a cmd is deleted.
+
+ In all other cases \a cmd is simply pushed on the stack.
+
+ If commands were undone before \a cmd was pushed, the current command and
+ all commands above it are deleted. Hence \a cmd always ends up being the
+ top-most on the stack.
+
+ Once a command is pushed, the stack takes ownership of it. There
+ are no getters to return the command, since modifying it after it has
+ been executed will almost always lead to corruption of the document's
+ state.
+
+ \sa QUndoCommand::id() QUndoCommand::mergeWith()
+*/
+
+void QUndoStack::push(QUndoCommand *cmd)
+{
+ Q_D(QUndoStack);
+ cmd->redo();
+
+ bool macro = !d->macro_stack.isEmpty();
+
+ QUndoCommand *cur = 0;
+ if (macro) {
+ QUndoCommand *macro_cmd = d->macro_stack.last();
+ if (!macro_cmd->d->child_list.isEmpty())
+ cur = macro_cmd->d->child_list.last();
+ } else {
+ if (d->index > 0)
+ cur = d->command_list.at(d->index - 1);
+ while (d->index < d->command_list.size())
+ delete d->command_list.takeLast();
+ if (d->clean_index > d->index)
+ d->clean_index = -1; // we've deleted the clean state
+ }
+
+ bool try_merge = cur != 0
+ && cur->id() != -1
+ && cur->id() == cmd->id()
+ && (macro || d->index != d->clean_index);
+
+ if (try_merge && cur->mergeWith(cmd)) {
+ delete cmd;
+ if (!macro) {
+ emit indexChanged(d->index);
+ emit canUndoChanged(canUndo());
+ emit undoTextChanged(undoText());
+ emit canRedoChanged(canRedo());
+ emit redoTextChanged(redoText());
+ }
+ } else {
+ if (macro) {
+ d->macro_stack.last()->d->child_list.append(cmd);
+ } else {
+ d->command_list.append(cmd);
+ d->checkUndoLimit();
+ d->setIndex(d->index + 1, false);
+ }
+ }
+}
+
+/*!
+ Marks the stack as clean and emits cleanChanged() if the stack was
+ not already clean.
+
+ Whenever the stack returns to this state through the use of undo/redo
+ commands, it emits the signal cleanChanged(). This signal is also
+ emitted when the stack leaves the clean state.
+
+ \sa isClean(), cleanIndex()
+*/
+
+void QUndoStack::setClean()
+{
+ Q_D(QUndoStack);
+ if (!d->macro_stack.isEmpty()) {
+ qWarning("QUndoStack::setClean(): cannot set clean in the middle of a macro");
+ return;
+ }
+
+ d->setIndex(d->index, true);
+}
+
+/*!
+ If the stack is in the clean state, returns true; otherwise returns false.
+
+ \sa setClean() cleanIndex()
+*/
+
+bool QUndoStack::isClean() const
+{
+ Q_D(const QUndoStack);
+ if (!d->macro_stack.isEmpty())
+ return false;
+ return d->clean_index == d->index;
+}
+
+/*!
+ Returns the clean index. This is the index at which setClean() was called.
+
+ A stack may not have a clean index. This happens if a document is saved,
+ some commands are undone, then a new command is pushed. Since
+ push() deletes all the undone commands before pushing the new command, the stack
+ can't return to the clean state again. In this case, this function returns -1.
+
+ \sa isClean() setClean()
+*/
+
+int QUndoStack::cleanIndex() const
+{
+ Q_D(const QUndoStack);
+ return d->clean_index;
+}
+
+/*!
+ Undoes the command below the current command by calling QUndoCommand::undo().
+ Decrements the current command index.
+
+ If the stack is empty, or if the bottom command on the stack has already been
+ undone, this function does nothing.
+
+ \sa redo() index()
+*/
+
+void QUndoStack::undo()
+{
+ Q_D(QUndoStack);
+ if (d->index == 0)
+ return;
+
+ if (!d->macro_stack.isEmpty()) {
+ qWarning("QUndoStack::undo(): cannot undo in the middle of a macro");
+ return;
+ }
+
+ int idx = d->index - 1;
+ d->command_list.at(idx)->undo();
+ d->setIndex(idx, false);
+}
+
+/*!
+ Redoes the current command by calling QUndoCommand::redo(). Increments the current
+ command index.
+
+ If the stack is empty, or if the top command on the stack has already been
+ redone, this function does nothing.
+
+ \sa undo() index()
+*/
+
+void QUndoStack::redo()
+{
+ Q_D(QUndoStack);
+ if (d->index == d->command_list.size())
+ return;
+
+ if (!d->macro_stack.isEmpty()) {
+ qWarning("QUndoStack::redo(): cannot redo in the middle of a macro");
+ return;
+ }
+
+ d->command_list.at(d->index)->redo();
+ d->setIndex(d->index + 1, false);
+}
+
+/*!
+ Returns the number of commands on the stack. Macro commands are counted as
+ one command.
+
+ \sa index() setIndex() command()
+*/
+
+int QUndoStack::count() const
+{
+ Q_D(const QUndoStack);
+ return d->command_list.size();
+}
+
+/*!
+ Returns the index of the current command. This is the command that will be
+ executed on the next call to redo(). It is not always the top-most command
+ on the stack, since a number of commands may have been undone.
+
+ \sa undo() redo() count()
+*/
+
+int QUndoStack::index() const
+{
+ Q_D(const QUndoStack);
+ return d->index;
+}
+
+/*!
+ Repeatedly calls undo() or redo() until the current command index reaches
+ \a idx. This function can be used to roll the state of the document forwards
+ of backwards. indexChanged() is emitted only once.
+
+ \sa index() count() undo() redo()
+*/
+
+void QUndoStack::setIndex(int idx)
+{
+ Q_D(QUndoStack);
+ if (!d->macro_stack.isEmpty()) {
+ qWarning("QUndoStack::setIndex(): cannot set index in the middle of a macro");
+ return;
+ }
+
+ if (idx < 0)
+ idx = 0;
+ else if (idx > d->command_list.size())
+ idx = d->command_list.size();
+
+ int i = d->index;
+ while (i < idx)
+ d->command_list.at(i++)->redo();
+ while (i > idx)
+ d->command_list.at(--i)->undo();
+
+ d->setIndex(idx, false);
+}
+
+/*!
+ Returns true if there is a command available for undo; otherwise returns false.
+
+ This function returns false if the stack is empty, or if the bottom command
+ on the stack has already been undone.
+
+ Synonymous with index() == 0.
+
+ \sa index() canRedo()
+*/
+
+bool QUndoStack::canUndo() const
+{
+ Q_D(const QUndoStack);
+ if (!d->macro_stack.isEmpty())
+ return false;
+ return d->index > 0;
+}
+
+/*!
+ Returns true if there is a command available for redo; otherwise returns false.
+
+ This function returns false if the stack is empty or if the top command
+ on the stack has already been redone.
+
+ Synonymous with index() == count().
+
+ \sa index() canUndo()
+*/
+
+bool QUndoStack::canRedo() const
+{
+ Q_D(const QUndoStack);
+ if (!d->macro_stack.isEmpty())
+ return false;
+ return d->index < d->command_list.size();
+}
+
+/*!
+ Returns the text of the command which will be undone in the next call to undo().
+
+ \sa QUndoCommand::actionText() redoText()
+*/
+
+QString QUndoStack::undoText() const
+{
+ Q_D(const QUndoStack);
+ if (!d->macro_stack.isEmpty())
+ return QString();
+ if (d->index > 0)
+ return d->command_list.at(d->index - 1)->actionText();
+ return QString();
+}
+
+/*!
+ Returns the text of the command which will be redone in the next call to redo().
+
+ \sa QUndoCommand::actionText() undoText()
+*/
+
+QString QUndoStack::redoText() const
+{
+ Q_D(const QUndoStack);
+ if (!d->macro_stack.isEmpty())
+ return QString();
+ if (d->index < d->command_list.size())
+ return d->command_list.at(d->index)->actionText();
+ return QString();
+}
+
+#ifndef QT_NO_ACTION
+
+/*!
+ Creates an undo QAction object with the given \a parent.
+
+ Triggering this action will cause a call to undo(). The text of this action
+ is the text of the command which will be undone in the next call to undo(),
+ prefixed by the specified \a prefix. If there is no command available for undo,
+ this action will be disabled.
+
+ If \a prefix is empty, the default template "Undo %1" is used instead of prefix.
+ Before Qt 4.8, the prefix "Undo" was used by default.
+
+ \sa createRedoAction(), canUndo(), QUndoCommand::text()
+*/
+
+QAction *QUndoStack::createUndoAction(QObject *parent, const QString &prefix) const
+{
+ QUndoAction *result = new QUndoAction(prefix, parent);
+ if (prefix.isEmpty())
+ result->setTextFormat(tr("Undo %1"), tr("Undo", "Default text for undo action"));
+
+ result->setEnabled(canUndo());
+ result->setPrefixedText(undoText());
+ connect(this, SIGNAL(canUndoChanged(bool)),
+ result, SLOT(setEnabled(bool)));
+ connect(this, SIGNAL(undoTextChanged(QString)),
+ result, SLOT(setPrefixedText(QString)));
+ connect(result, SIGNAL(triggered()), this, SLOT(undo()));
+ return result;
+}
+
+/*!
+ Creates an redo QAction object with the given \a parent.
+
+ Triggering this action will cause a call to redo(). The text of this action
+ is the text of the command which will be redone in the next call to redo(),
+ prefixed by the specified \a prefix. If there is no command available for redo,
+ this action will be disabled.
+
+ If \a prefix is empty, the default template "Redo %1" is used instead of prefix.
+ Before Qt 4.8, the prefix "Redo" was used by default.
+
+ \sa createUndoAction(), canRedo(), QUndoCommand::text()
+*/
+
+QAction *QUndoStack::createRedoAction(QObject *parent, const QString &prefix) const
+{
+ QUndoAction *result = new QUndoAction(prefix, parent);
+ if (prefix.isEmpty())
+ result->setTextFormat(tr("Redo %1"), tr("Redo", "Default text for redo action"));
+
+ result->setEnabled(canRedo());
+ result->setPrefixedText(redoText());
+ connect(this, SIGNAL(canRedoChanged(bool)),
+ result, SLOT(setEnabled(bool)));
+ connect(this, SIGNAL(redoTextChanged(QString)),
+ result, SLOT(setPrefixedText(QString)));
+ connect(result, SIGNAL(triggered()), this, SLOT(redo()));
+ return result;
+}
+
+#endif // QT_NO_ACTION
+
+/*!
+ Begins composition of a macro command with the given \a text description.
+
+ An empty command described by the specified \a text is pushed on the stack.
+ Any subsequent commands pushed on the stack will be appended to the empty
+ command's children until endMacro() is called.
+
+ Calls to beginMacro() and endMacro() may be nested, but every call to
+ beginMacro() must have a matching call to endMacro().
+
+ While a macro is composed, the stack is disabled. This means that:
+ \list
+ \i indexChanged() and cleanChanged() are not emitted,
+ \i canUndo() and canRedo() return false,
+ \i calling undo() or redo() has no effect,
+ \i the undo/redo actions are disabled.
+ \endlist
+
+ The stack becomes enabled and appropriate signals are emitted when endMacro()
+ is called for the outermost macro.
+
+ \snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 4
+
+ This code is equivalent to:
+
+ \snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 5
+
+ \sa endMacro()
+*/
+
+void QUndoStack::beginMacro(const QString &text)
+{
+ Q_D(QUndoStack);
+ QUndoCommand *cmd = new QUndoCommand();
+ cmd->setText(text);
+
+ if (d->macro_stack.isEmpty()) {
+ while (d->index < d->command_list.size())
+ delete d->command_list.takeLast();
+ if (d->clean_index > d->index)
+ d->clean_index = -1; // we've deleted the clean state
+ d->command_list.append(cmd);
+ } else {
+ d->macro_stack.last()->d->child_list.append(cmd);
+ }
+ d->macro_stack.append(cmd);
+
+ if (d->macro_stack.count() == 1) {
+ emit canUndoChanged(false);
+ emit undoTextChanged(QString());
+ emit canRedoChanged(false);
+ emit redoTextChanged(QString());
+ }
+}
+
+/*!
+ Ends composition of a macro command.
+
+ If this is the outermost macro in a set nested macros, this function emits
+ indexChanged() once for the entire macro command.
+
+ \sa beginMacro()
+*/
+
+void QUndoStack::endMacro()
+{
+ Q_D(QUndoStack);
+ if (d->macro_stack.isEmpty()) {
+ qWarning("QUndoStack::endMacro(): no matching beginMacro()");
+ return;
+ }
+
+ d->macro_stack.removeLast();
+
+ if (d->macro_stack.isEmpty()) {
+ d->checkUndoLimit();
+ d->setIndex(d->index + 1, false);
+ }
+}
+
+/*!
+ \since 4.4
+
+ Returns a const pointer to the command at \a index.
+
+ This function returns a const pointer, because modifying a command,
+ once it has been pushed onto the stack and executed, almost always
+ causes corruption of the state of the document, if the command is
+ later undone or redone.
+
+ \sa QUndoCommand::child()
+*/
+const QUndoCommand *QUndoStack::command(int index) const
+{
+ Q_D(const QUndoStack);
+
+ if (index < 0 || index >= d->command_list.count())
+ return 0;
+ return d->command_list.at(index);
+}
+
+/*!
+ Returns the text of the command at index \a idx.
+
+ \sa beginMacro()
+*/
+
+QString QUndoStack::text(int idx) const
+{
+ Q_D(const QUndoStack);
+
+ if (idx < 0 || idx >= d->command_list.size())
+ return QString();
+ return d->command_list.at(idx)->text();
+}
+
+/*!
+ \property QUndoStack::undoLimit
+ \brief the maximum number of commands on this stack.
+ \since 4.3
+
+ When the number of commands on a stack exceedes the stack's undoLimit, commands are
+ deleted from the bottom of the stack. Macro commands (commands with child commands)
+ are treated as one command. The default value is 0, which means that there is no
+ limit.
+
+ This property may only be set when the undo stack is empty, since setting it on a
+ non-empty stack might delete the command at the current index. Calling setUndoLimit()
+ on a non-empty stack prints a warning and does nothing.
+*/
+
+void QUndoStack::setUndoLimit(int limit)
+{
+ Q_D(QUndoStack);
+
+ if (!d->command_list.isEmpty()) {
+ qWarning("QUndoStack::setUndoLimit(): an undo limit can only be set when the stack is empty");
+ return;
+ }
+
+ if (limit == d->undo_limit)
+ return;
+ d->undo_limit = limit;
+ d->checkUndoLimit();
+}
+
+int QUndoStack::undoLimit() const
+{
+ Q_D(const QUndoStack);
+
+ return d->undo_limit;
+}
+
+/*!
+ \property QUndoStack::active
+ \brief the active status of this stack.
+
+ An application often has multiple undo stacks, one for each opened document. The active
+ stack is the one associated with the currently active document. If the stack belongs
+ to a QUndoGroup, calls to QUndoGroup::undo() or QUndoGroup::redo() will be forwarded
+ to this stack when it is active. If the QUndoGroup is watched by a QUndoView, the view
+ will display the contents of this stack when it is active. If the stack does not belong to
+ a QUndoGroup, making it active has no effect.
+
+ It is the programmer's responsibility to specify which stack is active by
+ calling setActive(), usually when the associated document window receives focus.
+
+ \sa QUndoGroup
+*/
+
+void QUndoStack::setActive(bool active)
+{
+#ifdef QT_NO_UNDOGROUP
+ Q_UNUSED(active);
+#else
+ Q_D(QUndoStack);
+
+ if (d->group != 0) {
+ if (active)
+ d->group->setActiveStack(this);
+ else if (d->group->activeStack() == this)
+ d->group->setActiveStack(0);
+ }
+#endif
+}
+
+bool QUndoStack::isActive() const
+{
+#ifdef QT_NO_UNDOGROUP
+ return true;
+#else
+ Q_D(const QUndoStack);
+ return d->group == 0 || d->group->activeStack() == this;
+#endif
+}
+
+/*!
+ \fn void QUndoStack::indexChanged(int idx)
+
+ This signal is emitted whenever a command modifies the state of the document.
+ This happens when a command is undone or redone. When a macro
+ command is undone or redone, or setIndex() is called, this signal
+ is emitted only once.
+
+ \a idx specifies the index of the current command, ie. the command which will be
+ executed on the next call to redo().
+
+ \sa index() setIndex()
+*/
+
+/*!
+ \fn void QUndoStack::cleanChanged(bool clean)
+
+ This signal is emitted whenever the stack enters or leaves the clean state.
+ If \a clean is true, the stack is in a clean state; otherwise this signal
+ indicates that it has left the clean state.
+
+ \sa isClean() setClean()
+*/
+
+/*!
+ \fn void QUndoStack::undoTextChanged(const QString &undoText)
+
+ This signal is emitted whenever the value of undoText() changes. It is
+ used to update the text property of the undo action returned by createUndoAction().
+ \a undoText specifies the new text.
+*/
+
+/*!
+ \fn void QUndoStack::canUndoChanged(bool canUndo)
+
+ This signal is emitted whenever the value of canUndo() changes. It is
+ used to enable or disable the undo action returned by createUndoAction().
+ \a canUndo specifies the new value.
+*/
+
+/*!
+ \fn void QUndoStack::redoTextChanged(const QString &redoText)
+
+ This signal is emitted whenever the value of redoText() changes. It is
+ used to update the text property of the redo action returned by createRedoAction().
+ \a redoText specifies the new text.
+*/
+
+/*!
+ \fn void QUndoStack::canRedoChanged(bool canRedo)
+
+ This signal is emitted whenever the value of canRedo() changes. It is
+ used to enable or disable the redo action returned by createRedoAction().
+ \a canRedo specifies the new value.
+*/
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_UNDOSTACK
diff --git a/src/widgets/util/qundostack.h b/src/widgets/util/qundostack.h
new file mode 100644
index 0000000000..493d11fdb7
--- /dev/null
+++ b/src/widgets/util/qundostack.h
@@ -0,0 +1,159 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QUNDOSTACK_H
+#define QUNDOSTACK_H
+
+#include <QtCore/qobject.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Gui)
+
+class QAction;
+class QUndoCommandPrivate;
+class QUndoStackPrivate;
+
+#ifndef QT_NO_UNDOCOMMAND
+
+class Q_WIDGETS_EXPORT QUndoCommand
+{
+ QUndoCommandPrivate *d;
+
+public:
+ explicit QUndoCommand(QUndoCommand *parent = 0);
+ explicit QUndoCommand(const QString &text, QUndoCommand *parent = 0);
+ virtual ~QUndoCommand();
+
+ virtual void undo();
+ virtual void redo();
+
+ QString text() const;
+ QString actionText() const;
+ void setText(const QString &text);
+
+ virtual int id() const;
+ virtual bool mergeWith(const QUndoCommand *other);
+
+ int childCount() const;
+ const QUndoCommand *child(int index) const;
+
+private:
+ Q_DISABLE_COPY(QUndoCommand)
+ friend class QUndoStack;
+};
+
+#endif // QT_NO_UNDOCOMMAND
+
+#ifndef QT_NO_UNDOSTACK
+
+class Q_WIDGETS_EXPORT QUndoStack : public QObject
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(QUndoStack)
+ Q_PROPERTY(bool active READ isActive WRITE setActive)
+ Q_PROPERTY(int undoLimit READ undoLimit WRITE setUndoLimit)
+
+public:
+ explicit QUndoStack(QObject *parent = 0);
+ ~QUndoStack();
+ void clear();
+
+ void push(QUndoCommand *cmd);
+
+ bool canUndo() const;
+ bool canRedo() const;
+ QString undoText() const;
+ QString redoText() const;
+
+ int count() const;
+ int index() const;
+ QString text(int idx) const;
+
+#ifndef QT_NO_ACTION
+ QAction *createUndoAction(QObject *parent,
+ const QString &prefix = QString()) const;
+ QAction *createRedoAction(QObject *parent,
+ const QString &prefix = QString()) const;
+#endif // QT_NO_ACTION
+
+ bool isActive() const;
+ bool isClean() const;
+ int cleanIndex() const;
+
+ void beginMacro(const QString &text);
+ void endMacro();
+
+ void setUndoLimit(int limit);
+ int undoLimit() const;
+
+ const QUndoCommand *command(int index) const;
+
+public Q_SLOTS:
+ void setClean();
+ void setIndex(int idx);
+ void undo();
+ void redo();
+ void setActive(bool active = true);
+
+Q_SIGNALS:
+ void indexChanged(int idx);
+ void cleanChanged(bool clean);
+ void canUndoChanged(bool canUndo);
+ void canRedoChanged(bool canRedo);
+ void undoTextChanged(const QString &undoText);
+ void redoTextChanged(const QString &redoText);
+
+private:
+ Q_DISABLE_COPY(QUndoStack)
+ friend class QUndoGroup;
+};
+
+#endif // QT_NO_UNDOSTACK
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QUNDOSTACK_H
diff --git a/src/widgets/util/qundostack_p.h b/src/widgets/util/qundostack_p.h
new file mode 100644
index 0000000000..1e5f99bc6c
--- /dev/null
+++ b/src/widgets/util/qundostack_p.h
@@ -0,0 +1,114 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QUNDOSTACK_P_H
+#define QUNDOSTACK_P_H
+
+#include <private/qobject_p.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qstring.h>
+#include <QtWidgets/qaction.h>
+
+#include "qundostack.h"
+
+QT_BEGIN_NAMESPACE
+class QUndoCommand;
+class QUndoGroup;
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of qapplication_*.cpp, qwidget*.cpp and qfiledialog.cpp. This header
+// file may change from version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+class QUndoCommandPrivate
+{
+public:
+ QUndoCommandPrivate() : id(-1) {}
+ QList<QUndoCommand*> child_list;
+ QString text;
+ QString actionText;
+ int id;
+};
+
+#ifndef QT_NO_UNDOSTACK
+
+class QUndoStackPrivate : public QObjectPrivate
+{
+ Q_DECLARE_PUBLIC(QUndoStack)
+public:
+ QUndoStackPrivate() : index(0), clean_index(0), group(0), undo_limit(0) {}
+
+ QList<QUndoCommand*> command_list;
+ QList<QUndoCommand*> macro_stack;
+ int index;
+ int clean_index;
+ QUndoGroup *group;
+ int undo_limit;
+
+ void setIndex(int idx, bool clean);
+ bool checkUndoLimit();
+};
+
+#ifndef QT_NO_ACTION
+class QUndoAction : public QAction
+{
+ Q_OBJECT
+public:
+ QUndoAction(const QString &prefix, QObject *parent = 0);
+ void setTextFormat(const QString &textFormat, const QString &defaultText);
+public Q_SLOTS:
+ void setPrefixedText(const QString &text);
+private:
+ QString m_prefix;
+ QString m_defaultText;
+};
+#endif // QT_NO_ACTION
+
+
+QT_END_NAMESPACE
+#endif // QT_NO_UNDOSTACK
+#endif // QUNDOSTACK_P_H
diff --git a/src/widgets/util/qundoview.cpp b/src/widgets/util/qundoview.cpp
new file mode 100644
index 0000000000..820fcaf2de
--- /dev/null
+++ b/src/widgets/util/qundoview.cpp
@@ -0,0 +1,476 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qundostack.h"
+#include "qundoview.h"
+
+#ifndef QT_NO_UNDOVIEW
+
+#include "qundogroup.h"
+#include <QtCore/qabstractitemmodel.h>
+#include <QtCore/qpointer.h>
+#include <QtWidgets/qicon.h>
+#include <private/qlistview_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QUndoModel : public QAbstractItemModel
+{
+ Q_OBJECT
+public:
+ QUndoModel(QObject *parent = 0);
+
+ QUndoStack *stack() const;
+
+ virtual QModelIndex index(int row, int column,
+ const QModelIndex &parent = QModelIndex()) const;
+ virtual QModelIndex parent(const QModelIndex &child) const;
+ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ virtual int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+
+ QModelIndex selectedIndex() const;
+ QItemSelectionModel *selectionModel() const;
+
+ QString emptyLabel() const;
+ void setEmptyLabel(const QString &label);
+
+ void setCleanIcon(const QIcon &icon);
+ QIcon cleanIcon() const;
+
+public slots:
+ void setStack(QUndoStack *stack);
+
+private slots:
+ void stackChanged();
+ void stackDestroyed(QObject *obj);
+ void setStackCurrentIndex(const QModelIndex &index);
+
+private:
+ QUndoStack *m_stack;
+ QItemSelectionModel *m_sel_model;
+ QString m_emty_label;
+ QIcon m_clean_icon;
+};
+
+QUndoModel::QUndoModel(QObject *parent)
+ : QAbstractItemModel(parent)
+{
+ m_stack = 0;
+ m_sel_model = new QItemSelectionModel(this, this);
+ connect(m_sel_model, SIGNAL(currentChanged(QModelIndex,QModelIndex)),
+ this, SLOT(setStackCurrentIndex(QModelIndex)));
+ m_emty_label = tr("<empty>");
+}
+
+QItemSelectionModel *QUndoModel::selectionModel() const
+{
+ return m_sel_model;
+}
+
+QUndoStack *QUndoModel::stack() const
+{
+ return m_stack;
+}
+
+void QUndoModel::setStack(QUndoStack *stack)
+{
+ if (m_stack == stack)
+ return;
+
+ if (m_stack != 0) {
+ disconnect(m_stack, SIGNAL(cleanChanged(bool)), this, SLOT(stackChanged()));
+ disconnect(m_stack, SIGNAL(indexChanged(int)), this, SLOT(stackChanged()));
+ disconnect(m_stack, SIGNAL(destroyed(QObject*)), this, SLOT(stackDestroyed(QObject*)));
+ }
+ m_stack = stack;
+ if (m_stack != 0) {
+ connect(m_stack, SIGNAL(cleanChanged(bool)), this, SLOT(stackChanged()));
+ connect(m_stack, SIGNAL(indexChanged(int)), this, SLOT(stackChanged()));
+ connect(m_stack, SIGNAL(destroyed(QObject*)), this, SLOT(stackDestroyed(QObject*)));
+ }
+
+ stackChanged();
+}
+
+void QUndoModel::stackDestroyed(QObject *obj)
+{
+ if (obj != m_stack)
+ return;
+ m_stack = 0;
+
+ stackChanged();
+}
+
+void QUndoModel::stackChanged()
+{
+ reset();
+ m_sel_model->setCurrentIndex(selectedIndex(), QItemSelectionModel::ClearAndSelect);
+}
+
+void QUndoModel::setStackCurrentIndex(const QModelIndex &index)
+{
+ if (m_stack == 0)
+ return;
+
+ if (index == selectedIndex())
+ return;
+
+ if (index.column() != 0)
+ return;
+
+ m_stack->setIndex(index.row());
+}
+
+QModelIndex QUndoModel::selectedIndex() const
+{
+ return m_stack == 0 ? QModelIndex() : createIndex(m_stack->index(), 0);
+}
+
+QModelIndex QUndoModel::index(int row, int column, const QModelIndex &parent) const
+{
+ if (m_stack == 0)
+ return QModelIndex();
+
+ if (parent.isValid())
+ return QModelIndex();
+
+ if (column != 0)
+ return QModelIndex();
+
+ if (row < 0 || row > m_stack->count())
+ return QModelIndex();
+
+ return createIndex(row, column);
+}
+
+QModelIndex QUndoModel::parent(const QModelIndex&) const
+{
+ return QModelIndex();
+}
+
+int QUndoModel::rowCount(const QModelIndex &parent) const
+{
+ if (m_stack == 0)
+ return 0;
+
+ if (parent.isValid())
+ return 0;
+
+ return m_stack->count() + 1;
+}
+
+int QUndoModel::columnCount(const QModelIndex&) const
+{
+ return 1;
+}
+
+QVariant QUndoModel::data(const QModelIndex &index, int role) const
+{
+ if (m_stack == 0)
+ return QVariant();
+
+ if (index.column() != 0)
+ return QVariant();
+
+ if (index.row() < 0 || index.row() > m_stack->count())
+ return QVariant();
+
+ if (role == Qt::DisplayRole) {
+ if (index.row() == 0)
+ return m_emty_label;
+ return m_stack->text(index.row() - 1);
+ } else if (role == Qt::DecorationRole) {
+ if (index.row() == m_stack->cleanIndex() && !m_clean_icon.isNull())
+ return m_clean_icon;
+ return QVariant();
+ }
+
+ return QVariant();
+}
+
+QString QUndoModel::emptyLabel() const
+{
+ return m_emty_label;
+}
+
+void QUndoModel::setEmptyLabel(const QString &label)
+{
+ m_emty_label = label;
+ stackChanged();
+}
+
+void QUndoModel::setCleanIcon(const QIcon &icon)
+{
+ m_clean_icon = icon;
+ stackChanged();
+}
+
+QIcon QUndoModel::cleanIcon() const
+{
+ return m_clean_icon;
+}
+
+/*!
+ \class QUndoView
+ \brief The QUndoView class displays the contents of a QUndoStack.
+ \since 4.2
+
+ \ingroup advanced
+
+ QUndoView is a QListView which displays the list of commands pushed on an undo stack.
+ The most recently executed command is always selected. Selecting a different command
+ results in a call to QUndoStack::setIndex(), rolling the state of the document
+ backwards or forward to the new command.
+
+ The stack can be set explicitly with setStack(). Alternatively, a QUndoGroup object can
+ be set with setGroup(). The view will then update itself automatically whenever the
+ active stack of the group changes.
+
+ \image qundoview.png
+*/
+
+class QUndoViewPrivate : public QListViewPrivate
+{
+ Q_DECLARE_PUBLIC(QUndoView)
+public:
+ QUndoViewPrivate() :
+#ifndef QT_NO_UNDOGROUP
+ group(0),
+#endif
+ model(0) {}
+
+#ifndef QT_NO_UNDOGROUP
+ QPointer<QUndoGroup> group;
+#endif
+ QUndoModel *model;
+
+ void init();
+};
+
+void QUndoViewPrivate::init()
+{
+ Q_Q(QUndoView);
+
+ model = new QUndoModel(q);
+ q->setModel(model);
+ q->setSelectionModel(model->selectionModel());
+}
+
+/*!
+ Constructs a new view with parent \a parent.
+*/
+
+QUndoView::QUndoView(QWidget *parent)
+ : QListView(*new QUndoViewPrivate(), parent)
+{
+ Q_D(QUndoView);
+ d->init();
+}
+
+/*!
+ Constructs a new view with parent \a parent and sets the observed stack to \a stack.
+*/
+
+QUndoView::QUndoView(QUndoStack *stack, QWidget *parent)
+ : QListView(*new QUndoViewPrivate(), parent)
+{
+ Q_D(QUndoView);
+ d->init();
+ setStack(stack);
+}
+
+#ifndef QT_NO_UNDOGROUP
+
+/*!
+ Constructs a new view with parent \a parent and sets the observed group to \a group.
+
+ The view will update itself autmiatically whenever the active stack of the group changes.
+*/
+
+QUndoView::QUndoView(QUndoGroup *group, QWidget *parent)
+ : QListView(*new QUndoViewPrivate(), parent)
+{
+ Q_D(QUndoView);
+ d->init();
+ setGroup(group);
+}
+
+#endif // QT_NO_UNDOGROUP
+
+/*!
+ Destroys this view.
+*/
+
+QUndoView::~QUndoView()
+{
+}
+
+/*!
+ Returns the stack currently displayed by this view. If the view is looking at a
+ QUndoGroup, this the group's active stack.
+
+ \sa setStack() setGroup()
+*/
+
+QUndoStack *QUndoView::stack() const
+{
+ Q_D(const QUndoView);
+ return d->model->stack();
+}
+
+/*!
+ Sets the stack displayed by this view to \a stack. If \a stack is 0, the view
+ will be empty.
+
+ If the view was previously looking at a QUndoGroup, the group is set to 0.
+
+ \sa stack() setGroup()
+*/
+
+void QUndoView::setStack(QUndoStack *stack)
+{
+ Q_D(QUndoView);
+#ifndef QT_NO_UNDOGROUP
+ setGroup(0);
+#endif
+ d->model->setStack(stack);
+}
+
+#ifndef QT_NO_UNDOGROUP
+
+/*!
+ Sets the group displayed by this view to \a group. If \a group is 0, the view will
+ be empty.
+
+ The view will update itself autmiatically whenever the active stack of the group changes.
+
+ \sa group() setStack()
+*/
+
+void QUndoView::setGroup(QUndoGroup *group)
+{
+ Q_D(QUndoView);
+
+ if (d->group == group)
+ return;
+
+ if (d->group != 0) {
+ disconnect(d->group, SIGNAL(activeStackChanged(QUndoStack*)),
+ d->model, SLOT(setStack(QUndoStack*)));
+ }
+
+ d->group = group;
+
+ if (d->group != 0) {
+ connect(d->group, SIGNAL(activeStackChanged(QUndoStack*)),
+ d->model, SLOT(setStack(QUndoStack*)));
+ d->model->setStack(d->group->activeStack());
+ } else {
+ d->model->setStack(0);
+ }
+}
+
+/*!
+ Returns the group displayed by this view.
+
+ If the view is not looking at group, this function returns 0.
+
+ \sa setGroup() setStack()
+*/
+
+QUndoGroup *QUndoView::group() const
+{
+ Q_D(const QUndoView);
+ return d->group;
+}
+
+#endif // QT_NO_UNDOGROUP
+
+/*!
+ \property QUndoView::emptyLabel
+ \brief the label used for the empty state.
+
+ The empty label is the topmost element in the list of commands, which represents
+ the state of the document before any commands were pushed on the stack. The default
+ is the string "<empty>".
+*/
+
+void QUndoView::setEmptyLabel(const QString &label)
+{
+ Q_D(QUndoView);
+ d->model->setEmptyLabel(label);
+}
+
+QString QUndoView::emptyLabel() const
+{
+ Q_D(const QUndoView);
+ return d->model->emptyLabel();
+}
+
+/*!
+ \property QUndoView::cleanIcon
+ \brief the icon used to represent the clean state.
+
+ A stack may have a clean state set with QUndoStack::setClean(). This is usually
+ the state of the document at the point it was saved. QUndoView can display an
+ icon in the list of commands to show the clean state. If this property is
+ a null icon, no icon is shown. The default value is the null icon.
+*/
+
+void QUndoView::setCleanIcon(const QIcon &icon)
+{
+ Q_D(const QUndoView);
+ d->model->setCleanIcon(icon);
+
+}
+
+QIcon QUndoView::cleanIcon() const
+{
+ Q_D(const QUndoView);
+ return d->model->cleanIcon();
+}
+
+QT_END_NAMESPACE
+
+#include "qundoview.moc"
+
+#endif // QT_NO_UNDOVIEW
diff --git a/src/widgets/util/qundoview.h b/src/widgets/util/qundoview.h
new file mode 100644
index 0000000000..9df2995f9d
--- /dev/null
+++ b/src/widgets/util/qundoview.h
@@ -0,0 +1,102 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.1, 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.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QUNDOVIEW_H
+#define QUNDOVIEW_H
+
+#include <QtWidgets/qlistview.h>
+#include <QtCore/qstring.h>
+
+#ifndef QT_NO_UNDOVIEW
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+class QUndoViewPrivate;
+class QUndoStack;
+class QUndoGroup;
+class QIcon;
+
+QT_MODULE(Gui)
+
+class Q_WIDGETS_EXPORT QUndoView : public QListView
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(QUndoView)
+ Q_PROPERTY(QString emptyLabel READ emptyLabel WRITE setEmptyLabel)
+ Q_PROPERTY(QIcon cleanIcon READ cleanIcon WRITE setCleanIcon)
+
+public:
+ explicit QUndoView(QWidget *parent = 0);
+ explicit QUndoView(QUndoStack *stack, QWidget *parent = 0);
+#ifndef QT_NO_UNDOGROUP
+ explicit QUndoView(QUndoGroup *group, QWidget *parent = 0);
+#endif
+ ~QUndoView();
+
+ QUndoStack *stack() const;
+#ifndef QT_NO_UNDOGROUP
+ QUndoGroup *group() const;
+#endif
+
+ void setEmptyLabel(const QString &label);
+ QString emptyLabel() const;
+
+ void setCleanIcon(const QIcon &icon);
+ QIcon cleanIcon() const;
+
+public Q_SLOTS:
+ void setStack(QUndoStack *stack);
+#ifndef QT_NO_UNDOGROUP
+ void setGroup(QUndoGroup *group);
+#endif
+
+private:
+ Q_DISABLE_COPY(QUndoView)
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QT_NO_UNDOVIEW
+#endif // QUNDOVIEW_H
diff --git a/src/widgets/util/util.pri b/src/widgets/util/util.pri
new file mode 100644
index 0000000000..1ba8011ee3
--- /dev/null
+++ b/src/widgets/util/util.pri
@@ -0,0 +1,69 @@
+# Qt util module
+
+HEADERS += \
+ util/qsystemtrayicon.h \
+ util/qcompleter.h \
+ util/qcompleter_p.h \
+ util/qsystemtrayicon_p.h \
+ util/qscroller.h \
+ util/qscroller_p.h \
+ util/qscrollerproperties.h \
+ util/qscrollerproperties_p.h \
+ util/qflickgesture_p.h \
+ util/qundogroup.h \
+ util/qundostack.h \
+ util/qundostack_p.h \
+ util/qundoview.h
+
+SOURCES += \
+ util/qsystemtrayicon.cpp \
+ util/qcompleter.cpp \
+ util/qscroller.cpp \
+ util/qscrollerproperties.cpp \
+ util/qflickgesture.cpp \
+ util/qundogroup.cpp \
+ util/qundostack.cpp \
+ util/qundoview.cpp
+
+
+wince* {
+ SOURCES += \
+ util/qsystemtrayicon_wince.cpp
+} else:win32:!qpa {
+ SOURCES += \
+ util/qsystemtrayicon_win.cpp
+}
+
+unix:x11 {
+ SOURCES += \
+ util/qsystemtrayicon_x11.cpp
+}
+
+qpa {
+ SOURCES += \
+ util/qsystemtrayicon_qpa.cpp
+}
+
+!qpa:!x11:mac {
+ OBJECTIVE_SOURCES += util/qsystemtrayicon_mac.mm
+}
+
+symbian {
+ LIBS += -letext -lplatformenv
+ contains(S60_VERSION, 3.1)|contains(S60_VERSION, 3.2)|contains(S60_VERSION, 5.0) {
+ LIBS += -lsendas2 -lapmime
+ contains(QT_CONFIG, s60) {
+ contains(CONFIG, is_using_gnupoc) {
+ LIBS += -lcommonui
+ } else {
+ LIBS += -lCommonUI
+ }
+ }
+ } else {
+ DEFINES += USE_SCHEMEHANDLER
+ }
+}
+
+macx {
+ OBJECTIVE_SOURCES += util/qscroller_mac.mm
+}