summaryrefslogtreecommitdiffstats
path: root/src/gui/util
diff options
context:
space:
mode:
authorQt by Nokia <qt-info@nokia.com>2011-04-27 12:05:43 +0200
committeraxis <qt-info@nokia.com>2011-04-27 12:05:43 +0200
commit38be0d13830efd2d98281c645c3a60afe05ffece (patch)
tree6ea73f3ec77f7d153333779883e8120f82820abe /src/gui/util
Initial import from the monolithic Qt.
This is the beginning of revision history for this module. If you want to look at revision history older than this, please refer to the Qt Git wiki for how to use Git history grafting. At the time of writing, this wiki is located here: http://qt.gitorious.org/qt/pages/GitIntroductionWithQt If you have already performed the grafting and you don't see any history beyond this commit, try running "git log" with the "--follow" argument. Branched from the monolithic repo, Qt master branch, at commit 896db169ea224deb96c59ce8af800d019de63f12
Diffstat (limited to 'src/gui/util')
-rw-r--r--src/gui/util/qcompleter.cpp1833
-rw-r--r--src/gui/util/qcompleter.h171
-rw-r--r--src/gui/util/qcompleter_p.h264
-rw-r--r--src/gui/util/qdesktopservices.cpp309
-rw-r--r--src/gui/util/qdesktopservices.h91
-rw-r--r--src/gui/util/qdesktopservices_mac.cpp188
-rw-r--r--src/gui/util/qdesktopservices_qws.cpp93
-rw-r--r--src/gui/util/qdesktopservices_s60.cpp461
-rw-r--r--src/gui/util/qdesktopservices_win.cpp267
-rw-r--r--src/gui/util/qdesktopservices_x11.cpp242
-rw-r--r--src/gui/util/qflickgesture.cpp715
-rw-r--r--src/gui/util/qflickgesture_p.h113
-rw-r--r--src/gui/util/qscroller.cpp2059
-rw-r--r--src/gui/util/qscroller.h155
-rw-r--r--src/gui/util/qscroller_mac.mm71
-rw-r--r--src/gui/util/qscroller_p.h209
-rw-r--r--src/gui/util/qscrollerproperties.cpp393
-rw-r--r--src/gui/util/qscrollerproperties.h140
-rw-r--r--src/gui/util/qscrollerproperties_p.h94
-rw-r--r--src/gui/util/qsystemtrayicon.cpp674
-rw-r--r--src/gui/util/qsystemtrayicon.h132
-rw-r--r--src/gui/util/qsystemtrayicon_mac.mm578
-rw-r--r--src/gui/util/qsystemtrayicon_p.h186
-rw-r--r--src/gui/util/qsystemtrayicon_qws.cpp96
-rw-r--r--src/gui/util/qsystemtrayicon_win.cpp524
-rw-r--r--src/gui/util/qsystemtrayicon_wince.cpp293
-rw-r--r--src/gui/util/qsystemtrayicon_x11.cpp401
-rw-r--r--src/gui/util/qundogroup.cpp499
-rw-r--r--src/gui/util/qundogroup.h110
-rw-r--r--src/gui/util/qundostack.cpp1127
-rw-r--r--src/gui/util/qundostack.h158
-rw-r--r--src/gui/util/qundostack_p.h111
-rw-r--r--src/gui/util/qundoview.cpp476
-rw-r--r--src/gui/util/qundoview.h102
-rw-r--r--src/gui/util/util.pri71
35 files changed, 13406 insertions, 0 deletions
diff --git a/src/gui/util/qcompleter.cpp b/src/gui/util/qcompleter.cpp
new file mode 100644
index 0000000000..0cb3bbdf8c
--- /dev/null
+++ b/src/gui/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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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 "QtGui/qscrollbar.h"
+#include "QtGui/qstringlistmodel.h"
+#include "QtGui/qdirmodel.h"
+#include "QtGui/qfilesystemmodel.h"
+#include "QtGui/qheaderview.h"
+#include "QtGui/qlistview.h"
+#include "QtGui/qapplication.h"
+#include "QtGui/qevent.h"
+#include "QtGui/qheaderview.h"
+#include "QtGui/qdesktopwidget.h"
+#include "QtGui/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/gui/util/qcompleter.h b/src/gui/util/qcompleter.h
new file mode 100644
index 0000000000..ace2f0afb6
--- /dev/null
+++ b/src/gui/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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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_GUI_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/gui/util/qcompleter_p.h b/src/gui/util/qcompleter_p.h
new file mode 100644
index 0000000000..83e7a27636
--- /dev/null
+++ b/src/gui/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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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 "QtGui/qtreeview.h"
+#include "QtGui/qabstractproxymodel.h"
+#include "qcompleter.h"
+#include "QtGui/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/gui/util/qdesktopservices.cpp b/src/gui/util/qdesktopservices.cpp
new file mode 100644
index 0000000000..cb5a3ef560
--- /dev/null
+++ b/src/gui/util/qdesktopservices.cpp
@@ -0,0 +1,309 @@
+/****************************************************************************
+**
+** 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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qdesktopservices.h"
+
+#ifndef QT_NO_DESKTOPSERVICES
+
+#include <qdebug.h>
+
+#if defined(Q_WS_QWS) || defined(Q_WS_QPA)
+#include "qdesktopservices_qws.cpp"
+#elif defined(Q_WS_X11)
+#include "qdesktopservices_x11.cpp"
+#elif defined(Q_WS_WIN)
+#include "qdesktopservices_win.cpp"
+#elif defined(Q_WS_MAC)
+#include "qdesktopservices_mac.cpp"
+#elif defined(Q_OS_SYMBIAN)
+#include "qdesktopservices_s60.cpp"
+#endif
+
+#include <qhash.h>
+#include <qobject.h>
+#include <qcoreapplication.h>
+#include <qurl.h>
+#include <qmutex.h>
+
+QT_BEGIN_NAMESPACE
+
+class QOpenUrlHandlerRegistry : public QObject
+{
+ Q_OBJECT
+public:
+ inline QOpenUrlHandlerRegistry() : mutex(QMutex::Recursive) {}
+
+ QMutex mutex;
+
+ struct Handler
+ {
+ QObject *receiver;
+ QByteArray name;
+ };
+ typedef QHash<QString, Handler> HandlerHash;
+ HandlerHash handlers;
+
+public Q_SLOTS:
+ void handlerDestroyed(QObject *handler);
+
+};
+
+Q_GLOBAL_STATIC(QOpenUrlHandlerRegistry, handlerRegistry)
+
+void QOpenUrlHandlerRegistry::handlerDestroyed(QObject *handler)
+{
+ HandlerHash::Iterator it = handlers.begin();
+ while (it != handlers.end()) {
+ if (it->receiver == handler) {
+ it = handlers.erase(it);
+ } else {
+ ++it;
+ }
+ }
+}
+
+/*!
+ \class QDesktopServices
+ \brief The QDesktopServices class provides methods for accessing common desktop services.
+ \since 4.2
+ \ingroup desktop
+
+ Many desktop environments provide services that can be used by applications to
+ perform common tasks, such as opening a web page, in a way that is both consistent
+ and takes into account the user's application preferences.
+
+ This class contains functions that provide simple interfaces to these services
+ that indicate whether they succeeded or failed.
+
+ The openUrl() function is used to open files located at arbitrary URLs in external
+ applications. For URLs that correspond to resources on the local filing system
+ (where the URL scheme is "file"), a suitable application will be used to open the
+ file; otherwise, a web browser will be used to fetch and display the file.
+
+ The user's desktop settings control whether certain executable file types are
+ opened for browsing, or if they are executed instead. Some desktop environments
+ are configured to prevent users from executing files obtained from non-local URLs,
+ or to ask the user's permission before doing so.
+
+ \section1 URL Handlers
+
+ The behavior of the openUrl() function can be customized for individual URL
+ schemes to allow applications to override the default handling behavior for
+ certain types of URLs.
+
+ The dispatch mechanism allows only one custom handler to be used for each URL
+ scheme; this is set using the setUrlHandler() function. Each handler is
+ implemented as a slot which accepts only a single QUrl argument.
+
+ The existing handlers for each scheme can be removed with the
+ unsetUrlHandler() function. This returns the handling behavior for the given
+ scheme to the default behavior.
+
+ This system makes it easy to implement a help system, for example. Help could be
+ provided in labels and text browsers using \gui{help://myapplication/mytopic}
+ URLs, and by registering a handler it becomes possible to display the help text
+ inside the application:
+
+ \snippet doc/src/snippets/code/src_gui_util_qdesktopservices.cpp 0
+
+ If inside the handler you decide that you can't open the requested
+ URL, you can just call QDesktopServices::openUrl() again with the
+ same argument, and it will try to open the URL using the
+ appropriate mechanism for the user's desktop environment.
+
+ \sa QSystemTrayIcon, QProcess
+*/
+
+/*!
+ Opens the given \a url in the appropriate Web browser for the user's desktop
+ environment, and returns true if successful; otherwise returns false.
+
+ If the URL is a reference to a local file (i.e., the URL scheme is "file") then
+ it will be opened with a suitable application instead of a Web browser.
+
+ The following example opens a file on the Windows file system residing on a path
+ that contains spaces:
+
+ \snippet doc/src/snippets/code/src_gui_util_qdesktopservices.cpp 2
+
+ If a \c mailto URL is specified, the user's e-mail client will be used to open a
+ composer window containing the options specified in the URL, similar to the way
+ \c mailto links are handled by a Web browser.
+
+ For example, the following URL contains a recipient (\c{user@foo.com}), a
+ subject (\c{Test}), and a message body (\c{Just a test}):
+
+ \snippet doc/src/snippets/code/src_gui_util_qdesktopservices.cpp 1
+
+ \warning Although many e-mail clients can send attachments and are
+ Unicode-aware, the user may have configured their client without these features.
+ Also, certain e-mail clients (e.g., Lotus Notes) have problems with long URLs.
+
+ \sa setUrlHandler()
+*/
+bool QDesktopServices::openUrl(const QUrl &url)
+{
+ QOpenUrlHandlerRegistry *registry = handlerRegistry();
+ QMutexLocker locker(&registry->mutex);
+ static bool insideOpenUrlHandler = false;
+
+ if (!insideOpenUrlHandler) {
+ QOpenUrlHandlerRegistry::HandlerHash::ConstIterator handler = registry->handlers.constFind(url.scheme());
+ if (handler != registry->handlers.constEnd()) {
+ insideOpenUrlHandler = true;
+ bool result = QMetaObject::invokeMethod(handler->receiver, handler->name.constData(), Qt::DirectConnection, Q_ARG(QUrl, url));
+ insideOpenUrlHandler = false;
+ return result; // ### support bool slot return type
+ }
+ }
+
+ bool result;
+ if (url.scheme() == QLatin1String("file"))
+ result = openDocument(url);
+ else
+ result = launchWebBrowser(url);
+
+ return result;
+}
+
+/*!
+ Sets the handler for the given \a scheme to be the handler \a method provided by
+ the \a receiver object.
+
+ This function provides a way to customize the behavior of openUrl(). If openUrl()
+ is called with a URL with the specified \a scheme then the given \a method on the
+ \a receiver object is called instead of QDesktopServices launching an external
+ application.
+
+ The provided method must be implemented as a slot that only accepts a single QUrl
+ argument.
+
+ If setUrlHandler() is used to set a new handler for a scheme which already
+ has a handler, the existing handler is simply replaced with the new one.
+ Since QDesktopServices does not take ownership of handlers, no objects are
+ deleted when a handler is replaced.
+
+ Note that the handler will always be called from within the same thread that
+ calls QDesktopServices::openUrl().
+
+ \sa openUrl(), unsetUrlHandler()
+*/
+void QDesktopServices::setUrlHandler(const QString &scheme, QObject *receiver, const char *method)
+{
+ QOpenUrlHandlerRegistry *registry = handlerRegistry();
+ QMutexLocker locker(&registry->mutex);
+ if (!receiver) {
+ registry->handlers.remove(scheme);
+ return;
+ }
+ QOpenUrlHandlerRegistry::Handler h;
+ h.receiver = receiver;
+ h.name = method;
+ registry->handlers.insert(scheme, h);
+ QObject::connect(receiver, SIGNAL(destroyed(QObject*)),
+ registry, SLOT(handlerDestroyed(QObject*)));
+}
+
+/*!
+ Removes a previously set URL handler for the specified \a scheme.
+
+ \sa setUrlHandler()
+*/
+void QDesktopServices::unsetUrlHandler(const QString &scheme)
+{
+ setUrlHandler(scheme, 0, 0);
+}
+
+/*!
+ \enum QDesktopServices::StandardLocation
+ \since 4.4
+
+ This enum describes the different locations that can be queried by
+ QDesktopServices::storageLocation and QDesktopServices::displayName.
+
+ \value DesktopLocation Returns the user's desktop directory.
+ \value DocumentsLocation Returns the user's document.
+ \value FontsLocation Returns the user's fonts.
+ \value ApplicationsLocation Returns the user's applications.
+ \value MusicLocation Returns the users music.
+ \value MoviesLocation Returns the user's movies.
+ \value PicturesLocation Returns the user's pictures.
+ \value TempLocation Returns the system's temporary directory.
+ \value HomeLocation Returns the user's home directory.
+ \value DataLocation Returns a directory location where persistent
+ application data can be stored. QCoreApplication::applicationName
+ and QCoreApplication::organizationName should work on all
+ platforms.
+ \value CacheLocation Returns a directory location where user-specific
+ non-essential (cached) data should be written.
+
+ \sa storageLocation() displayName()
+*/
+
+/*!
+ \fn QString QDesktopServices::storageLocation(StandardLocation type)
+ \since 4.4
+
+ Returns the default system directory where files of \a type belong, or an empty string
+ if the location cannot be determined.
+
+ \note The storage location returned can be a directory that does not exist; i.e., it
+ may need to be created by the system or the user.
+
+ \note On Symbian OS, ApplicationsLocation always point /sys/bin folder on the same drive
+ with executable. FontsLocation always points to folder on ROM drive. Symbian OS does not
+ have desktop concept, DesktopLocation returns same path as DocumentsLocation.
+ Rest of the standard locations point to folder on same drive with executable, except
+ that if executable is in ROM the folder from C drive is returned.
+*/
+
+/*!
+ \fn QString QDesktopServices::displayName(StandardLocation type)
+
+ Returns a localized display name for the given location \a type or
+ an empty QString if no relevant location can be found.
+*/
+
+QT_END_NAMESPACE
+
+#include "qdesktopservices.moc"
+
+#endif // QT_NO_DESKTOPSERVICES
diff --git a/src/gui/util/qdesktopservices.h b/src/gui/util/qdesktopservices.h
new file mode 100644
index 0000000000..9d5657ecca
--- /dev/null
+++ b/src/gui/util/qdesktopservices.h
@@ -0,0 +1,91 @@
+/****************************************************************************
+**
+** 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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QDESKTOPSERVICES_H
+#define QDESKTOPSERVICES_H
+
+#include <QtCore/qstring.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Gui)
+
+#ifndef QT_NO_DESKTOPSERVICES
+
+class QStringList;
+class QUrl;
+class QObject;
+
+class Q_GUI_EXPORT QDesktopServices
+{
+public:
+ static bool openUrl(const QUrl &url);
+ static void setUrlHandler(const QString &scheme, QObject *receiver, const char *method);
+ static void unsetUrlHandler(const QString &scheme);
+
+ enum StandardLocation {
+ DesktopLocation,
+ DocumentsLocation,
+ FontsLocation,
+ ApplicationsLocation,
+ MusicLocation,
+ MoviesLocation,
+ PicturesLocation,
+ TempLocation,
+ HomeLocation,
+ DataLocation,
+ CacheLocation
+ };
+
+ static QString storageLocation(StandardLocation type);
+ static QString displayName(StandardLocation type);
+};
+
+#endif // QT_NO_DESKTOPSERVICES
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QDESKTOPSERVICES_H
+
diff --git a/src/gui/util/qdesktopservices_mac.cpp b/src/gui/util/qdesktopservices_mac.cpp
new file mode 100644
index 0000000000..e9868471cb
--- /dev/null
+++ b/src/gui/util/qdesktopservices_mac.cpp
@@ -0,0 +1,188 @@
+/****************************************************************************
+**
+** 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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QT_NO_DESKTOPSERVICES
+
+#include <qprocess.h>
+#include <qstringlist.h>
+#include <qdir.h>
+#include <qurl.h>
+#include <qstringlist.h>
+#include <private/qcore_mac_p.h>
+#include <qcoreapplication.h>
+
+#include <ApplicationServices/ApplicationServices.h>
+
+QT_BEGIN_NAMESPACE
+
+/*
+ Translates a QDesktopServices::StandardLocation into the mac equivalent.
+*/
+OSType translateLocation(QDesktopServices::StandardLocation type)
+{
+ switch (type) {
+ case QDesktopServices::DesktopLocation:
+ return kDesktopFolderType; break;
+
+ case QDesktopServices::DocumentsLocation:
+ return kDocumentsFolderType; break;
+
+ case QDesktopServices::FontsLocation:
+ // There are at least two different font directories on the mac: /Library/Fonts and ~/Library/Fonts.
+ // To select a specific one we have to specify a different first parameter when calling FSFindFolder.
+ return kFontsFolderType; break;
+
+ case QDesktopServices::ApplicationsLocation:
+ return kApplicationsFolderType; break;
+
+ case QDesktopServices::MusicLocation:
+ return kMusicDocumentsFolderType; break;
+
+ case QDesktopServices::MoviesLocation:
+ return kMovieDocumentsFolderType; break;
+
+ case QDesktopServices::PicturesLocation:
+ return kPictureDocumentsFolderType; break;
+
+ case QDesktopServices::TempLocation:
+ return kTemporaryFolderType; break;
+
+ case QDesktopServices::DataLocation:
+ return kApplicationSupportFolderType; break;
+
+ case QDesktopServices::CacheLocation:
+ return kCachedDataFolderType; break;
+
+ default:
+ return kDesktopFolderType; break;
+ }
+}
+
+static bool lsOpen(const QUrl &url)
+{
+ if (!url.isValid() || url.scheme().isEmpty())
+ return false;
+
+ QCFType<CFURLRef> cfUrl = CFURLCreateWithString(0, QCFString(QString::fromLatin1(url.toEncoded())), 0);
+ if (cfUrl == 0)
+ return false;
+
+ const OSStatus err = LSOpenCFURLRef(cfUrl, 0);
+ return (err == noErr);
+}
+
+static bool launchWebBrowser(const QUrl &url)
+{
+ return lsOpen(url);
+}
+
+static bool openDocument(const QUrl &file)
+{
+ if (!file.isValid())
+ return false;
+
+ // LSOpen does not work in this case, use QProcess open instead.
+ return QProcess::startDetached(QLatin1String("open"), QStringList() << file.toLocalFile());
+}
+
+/*
+ Constructs a full unicode path from a FSRef.
+*/
+static QString getFullPath(const FSRef &ref)
+{
+ QByteArray ba(2048, 0);
+ if (FSRefMakePath(&ref, reinterpret_cast<UInt8 *>(ba.data()), ba.size()) == noErr)
+ return QString::fromUtf8(ba).normalized(QString::NormalizationForm_C);
+ return QString();
+}
+
+QString QDesktopServices::storageLocation(StandardLocation type)
+{
+ if (type == HomeLocation)
+ return QDir::homePath();
+
+ if (type == TempLocation)
+ return QDir::tempPath();
+
+ short domain = kOnAppropriateDisk;
+
+ if (type == DataLocation || type == CacheLocation)
+ domain = kUserDomain;
+
+ // http://developer.apple.com/documentation/Carbon/Reference/Folder_Manager/Reference/reference.html
+ FSRef ref;
+ OSErr err = FSFindFolder(domain, translateLocation(type), false, &ref);
+ if (err)
+ return QString();
+
+ QString path = getFullPath(ref);
+
+ if (type == DataLocation || type == CacheLocation) {
+ if (QCoreApplication::organizationName().isEmpty() == false)
+ path += QLatin1Char('/') + QCoreApplication::organizationName();
+ if (QCoreApplication::applicationName().isEmpty() == false)
+ path += QLatin1Char('/') + QCoreApplication::applicationName();
+ }
+
+ return path;
+}
+
+QString QDesktopServices::displayName(StandardLocation type)
+{
+ if (QDesktopServices::HomeLocation == type)
+ return QObject::tr("Home");
+
+ FSRef ref;
+ OSErr err = FSFindFolder(kOnAppropriateDisk, translateLocation(type), false, &ref);
+ if (err)
+ return QString();
+
+ QCFString displayName;
+ err = LSCopyDisplayNameForRef(&ref, &displayName);
+ if (err)
+ return QString();
+
+ return static_cast<QString>(displayName);
+}
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_DESKTOPSERVICES
diff --git a/src/gui/util/qdesktopservices_qws.cpp b/src/gui/util/qdesktopservices_qws.cpp
new file mode 100644
index 0000000000..324fa51a62
--- /dev/null
+++ b/src/gui/util/qdesktopservices_qws.cpp
@@ -0,0 +1,93 @@
+/****************************************************************************
+**
+** 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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <qcoreapplication.h>
+#include <qdir.h>
+
+QT_BEGIN_NAMESPACE
+
+static bool launchWebBrowser(const QUrl &url)
+{
+ Q_UNUSED(url);
+ qWarning("QDesktopServices::launchWebBrowser not implemented");
+ return false;
+}
+
+static bool openDocument(const QUrl &file)
+{
+ Q_UNUSED(file);
+ qWarning("QDesktopServices::openDocument not implemented");
+ return false;
+}
+
+
+QString QDesktopServices::storageLocation(StandardLocation type)
+{
+ if (type == DataLocation) {
+ QString qwsDataHome = QLatin1String(qgetenv("QWS_DATA_HOME"));
+ if (qwsDataHome.isEmpty())
+ qwsDataHome = QDir::homePath() + QLatin1String("/.qws/share");
+ qwsDataHome += QLatin1String("/data/")
+ + QCoreApplication::organizationName() + QLatin1Char('/')
+ + QCoreApplication::applicationName();
+ return qwsDataHome;
+ }
+ if (type == QDesktopServices::CacheLocation) {
+ QString qwsCacheHome = QLatin1String(qgetenv("QWS_CACHE_HOME"));
+ if (qwsCacheHome.isEmpty())
+ qwsCacheHome = QDir::homePath() + QLatin1String("/.qws/cache/");
+ qwsCacheHome += QCoreApplication::organizationName() + QLatin1Char('/')
+ + QCoreApplication::applicationName();
+ return qwsCacheHome;
+ }
+
+ qWarning("QDesktopServices::storageLocation %d not implemented", type);
+ return QString();
+}
+
+QString QDesktopServices::displayName(StandardLocation type)
+{
+ Q_UNUSED(type);
+ qWarning("QDesktopServices::displayName not implemented");
+ return QString();
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/util/qdesktopservices_s60.cpp b/src/gui/util/qdesktopservices_s60.cpp
new file mode 100644
index 0000000000..8caeb74fec
--- /dev/null
+++ b/src/gui/util/qdesktopservices_s60.cpp
@@ -0,0 +1,461 @@
+/****************************************************************************
+**
+** 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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#include <qcoreapplication.h>
+#include <qdir.h>
+#include <qurl.h>
+#include <private/qcore_symbian_p.h>
+
+#include <f32file.h> // TDriveUnit etc
+#include <pathinfo.h> // PathInfo
+
+#ifndef USE_SCHEMEHANDLER
+#ifdef Q_WS_S60
+// This flag changes the implementation to use S60 CDcoumentHandler
+// instead of apparc when opening the files
+#define USE_DOCUMENTHANDLER
+#endif
+
+#include <txtrich.h> // CRichText
+#include <eikenv.h> // CEikonEnv
+#include <apgcli.h> // RApaLsSession
+#include <apgtask.h> // TApaTaskList, TApaTask
+#include <rsendas.h> // RSendAs
+#include <rsendasmessage.h> // RSendAsMessage
+
+#ifdef USE_DOCUMENTHANDLER
+#include <DocumentHandler.h> // CDocumentHandler
+#include <AknServerApp.h>
+#endif
+#else // USE_SCHEMEHANDLER
+#include <schemehandler.h>
+#endif
+
+QT_BEGIN_NAMESPACE
+
+_LIT(KCacheSubDir, "Cache\\");
+_LIT(KSysBin, "\\Sys\\Bin\\");
+_LIT(KBrowserPrefix, "4 " );
+_LIT(KFontsDir, "z:\\resource\\Fonts\\");
+
+#ifndef USE_SCHEMEHANDLER
+// copied from miutset.h, so we don't get a dependency into the app layer
+const TUid KUidMsgTypeSMTP = {0x10001028}; // 268439592
+const TUid KUidBrowser = { 0x10008D39 };
+
+template<class R>
+class QAutoClose
+{
+public:
+ QAutoClose(R& aObj) : mPtr(&aObj) {}
+ ~QAutoClose()
+ {
+ if (mPtr)
+ mPtr->Close();
+ }
+ void Forget()
+ {
+ mPtr = 0;
+ }
+private:
+ QAutoClose(const QAutoClose&);
+ QAutoClose& operator=(const QAutoClose&);
+private:
+ R* mPtr;
+};
+
+#ifdef USE_DOCUMENTHANDLER
+class QS60DocumentHandler : public MAknServerAppExitObserver
+{
+public:
+ QS60DocumentHandler() :docHandler(0) {}
+
+ ~QS60DocumentHandler() {
+ delete docHandler;
+ }
+
+ CDocumentHandler& documentHandler() {
+ // In case user calls openUrl twice subsequently, before the first embedded app is closed
+ // we use the same CDocumentHandler instance. Using same instance makes sure the first
+ // launched embedded app is closed and latter one gets embedded to our app.
+ // Using different instance would help only theoretically since user cannot interact with
+ // several embedded apps at the same time.
+ if(!docHandler) {
+ QT_TRAP_THROWING(docHandler = CDocumentHandler::NewL());
+ docHandler->SetExitObserver(this);
+ }
+ return *docHandler;
+ }
+
+private: // From MAknServerAppExitObserver
+ void HandleServerAppExit(TInt /*aReason*/) {
+ delete docHandler;
+ docHandler = 0;
+ }
+
+private:
+ CDocumentHandler* docHandler;
+};
+Q_GLOBAL_STATIC(QS60DocumentHandler, qt_s60_documenthandler);
+#endif
+
+static void handleMailtoSchemeLX(const QUrl &url)
+{
+ // this function has many intermingled leaves and throws. Qt and Symbian objects do not have
+ // destructor dependencies, and cleanup object is used to prevent cleanup stack dependency on stack.
+ QString recipient = url.path();
+ QString subject = url.queryItemValue(QLatin1String("subject"));
+ QString body = url.queryItemValue(QLatin1String("body"));
+ QString to = url.queryItemValue(QLatin1String("to"));
+ QString cc = url.queryItemValue(QLatin1String("cc"));
+ QString bcc = url.queryItemValue(QLatin1String("bcc"));
+
+ // these fields might have comma separated addresses
+ QStringList recipients = recipient.split(QLatin1String(","), QString::SkipEmptyParts);
+ QStringList tos = to.split(QLatin1String(","), QString::SkipEmptyParts);
+ QStringList ccs = cc.split(QLatin1String(","), QString::SkipEmptyParts);
+ QStringList bccs = bcc.split(QLatin1String(","), QString::SkipEmptyParts);
+
+ RSendAs sendAs;
+ User::LeaveIfError(sendAs.Connect());
+ QAutoClose<RSendAs> sendAsCleanup(sendAs);
+
+ CSendAsAccounts* accounts = CSendAsAccounts::NewL();
+ CleanupStack::PushL(accounts);
+ sendAs.AvailableAccountsL(KUidMsgTypeSMTP, *accounts);
+ TInt count = accounts->Count();
+ CleanupStack::PopAndDestroy(accounts);
+
+ if(!count) {
+ // TODO: Task 259192: We should try to create account if count == 0
+ // CSendUi would provide account creation service for us, but it requires ridicilous
+ // capabilities: LocalServices NetworkServices ReadDeviceData ReadUserData WriteDeviceData WriteUserData
+ User::Leave(KErrNotSupported);
+ } else {
+ RSendAsMessage sendAsMessage;
+ sendAsMessage.CreateL(sendAs, KUidMsgTypeSMTP);
+ QAutoClose<RSendAsMessage> sendAsMessageCleanup(sendAsMessage);
+
+
+ // Subject
+ sendAsMessage.SetSubjectL(qt_QString2TPtrC(subject));
+
+ // Body
+ sendAsMessage.SetBodyTextL(qt_QString2TPtrC(body));
+
+ // To
+ foreach(QString item, recipients)
+ sendAsMessage.AddRecipientL(qt_QString2TPtrC(item), RSendAsMessage::ESendAsRecipientTo);
+
+ foreach(QString item, tos)
+ sendAsMessage.AddRecipientL(qt_QString2TPtrC(item), RSendAsMessage::ESendAsRecipientTo);
+
+ // Cc
+ foreach(QString item, ccs)
+ sendAsMessage.AddRecipientL(qt_QString2TPtrC(item), RSendAsMessage::ESendAsRecipientCc);
+
+ // Bcc
+ foreach(QString item, bccs)
+ sendAsMessage.AddRecipientL(qt_QString2TPtrC(item), RSendAsMessage::ESendAsRecipientBcc);
+
+ // send the message
+ sendAsMessage.LaunchEditorAndCloseL();
+ // sendAsMessage is already closed
+ sendAsMessageCleanup.Forget();
+ }
+}
+
+static bool handleMailtoScheme(const QUrl &url)
+{
+ TRAPD(err, QT_TRYCATCH_LEAVING(handleMailtoSchemeLX(url)));
+ return err ? false : true;
+}
+
+static void handleOtherSchemesL(const TDesC& aUrl)
+{
+ // Other schemes are at the moment passed to WEB browser
+ HBufC* buf16 = HBufC::NewLC(aUrl.Length() + KBrowserPrefix.iTypeLength);
+ buf16->Des().Copy(KBrowserPrefix); // Prefix used to launch correct browser view
+ buf16->Des().Append(aUrl);
+
+ TApaTaskList taskList(CEikonEnv::Static()->WsSession());
+ TApaTask task = taskList.FindApp(KUidBrowser);
+ if (task.Exists()){
+ // Switch to existing browser instance
+ task.BringToForeground();
+ HBufC8* param8 = HBufC8::NewLC(buf16->Length());
+ param8->Des().Append(buf16->Des());
+ task.SendMessage(TUid::Uid( 0 ), *param8); // Uid is not used
+ CleanupStack::PopAndDestroy(param8);
+ } else {
+ // Start a new browser instance
+ RApaLsSession appArcSession;
+ User::LeaveIfError(appArcSession.Connect());
+ CleanupClosePushL<RApaLsSession>(appArcSession);
+ TThreadId id;
+ appArcSession.StartDocument(*buf16, KUidBrowser, id);
+ CleanupStack::PopAndDestroy(); // appArcSession
+ }
+
+ CleanupStack::PopAndDestroy(buf16);
+}
+
+static bool handleOtherSchemes(const QUrl &url)
+{
+ QString encUrl(QString::fromUtf8(url.toEncoded()));
+ TPtrC urlPtr(qt_QString2TPtrC(encUrl));
+ TRAPD( err, handleOtherSchemesL(urlPtr));
+ return err ? false : true;
+}
+
+
+static void openDocumentL(const TDesC& aUrl)
+{
+#ifndef USE_DOCUMENTHANDLER
+ // Start app associated to file MIME type by using RApaLsSession
+ // Apparc base method cannot be used to open app in embedded mode,
+ // but seems to be most stable way at the moment
+ RApaLsSession appArcSession;
+ User::LeaveIfError(appArcSession.Connect());
+ CleanupClosePushL<RApaLsSession>(appArcSession);
+ TThreadId id;
+ // ESwitchFiles means do not start another instance
+ // Leaves if file does not exist, leave is trapped in openDocument and false returned to user.
+ User::LeaveIfError(appArcSession.StartDocument(aUrl, id,
+ RApaLsSession::ESwitchFiles)); // ELaunchNewApp
+ CleanupStack::PopAndDestroy(); // appArcSession
+#else
+ // This is an alternative way to launch app associated to MIME type
+ // CDocumentHandler also supports opening apps in embedded mode.
+ TDataType temp;
+ qt_s60_documenthandler()->documentHandler().OpenFileEmbeddedL(aUrl, temp);
+#endif
+}
+
+static bool launchWebBrowser(const QUrl &url)
+{
+ if (!url.isValid())
+ return false;
+
+ if (url.scheme() == QLatin1String("mailto")) {
+ return handleMailtoScheme(url);
+ }
+ return handleOtherSchemes( url );
+}
+
+static bool openDocument(const QUrl &file)
+{
+ if (!file.isValid())
+ return false;
+
+ QString filePath = file.toLocalFile();
+ filePath = QDir::toNativeSeparators(filePath);
+ TPtrC filePathPtr(qt_QString2TPtrC(filePath));
+ TRAPD(err, openDocumentL(filePathPtr));
+ return err ? false : true;
+}
+
+#else //USE_SCHEMEHANDLER
+// The schemehandler component only exist in private SDK. This implementation
+// exist here just for convenience in case that we need to use it later on
+// The schemehandle based implementation is not yet tested.
+
+// The biggest advantage of schemehandler is that it can handle
+// wide range of schemes and is extensible by plugins
+static void handleUrlL(const TDesC& aUrl)
+{
+ CSchemeHandler* schemeHandler = CSchemeHandler::NewL(aUrl);
+ CleanupStack::PushL(schemeHandler);
+ schemeHandler->HandleUrlStandaloneL(); // Process the Url in standalone mode
+ CleanupStack::PopAndDestroy();
+}
+
+static bool handleUrl(const QUrl &url)
+{
+ if (!url.isValid())
+ return false;
+
+ QString urlString(url.toEncoded());
+ TPtrC urlPtr(qt_QString2TPtrC(urlString));
+ TRAPD( err, handleUrlL(urlPtr));
+ return err ? false : true;
+}
+
+static bool launchWebBrowser(const QUrl &url)
+{
+ return handleUrl(url);
+}
+
+static bool openDocument(const QUrl &file)
+{
+ return handleUrl(file);
+}
+
+#endif //USE_SCHEMEHANDLER
+
+// Common functions to all implementations
+
+static TDriveUnit exeDrive()
+{
+ RProcess me;
+ TFileName processFileName = me.FileName();
+ TDriveUnit drive(processFileName);
+ return drive;
+}
+
+static TDriveUnit writableExeDrive()
+{
+ TDriveUnit drive = exeDrive();
+ if (drive.operator TInt() == EDriveZ)
+ return TDriveUnit(EDriveC);
+ return drive;
+}
+
+static TPtrC writableDataRoot()
+{
+ TDriveUnit drive = exeDrive();
+ switch (drive.operator TInt()){
+ case EDriveC:
+ return PathInfo::PhoneMemoryRootPath();
+ break;
+ case EDriveE:
+ return PathInfo::MemoryCardRootPath();
+ break;
+ case EDriveZ:
+ // It is not possible to write on ROM drive ->
+ // return phone mem root path instead
+ return PathInfo::PhoneMemoryRootPath();
+ break;
+ default:
+ return PathInfo::PhoneMemoryRootPath();
+ break;
+ }
+}
+
+QString QDesktopServices::storageLocation(StandardLocation type)
+{
+ TFileName path;
+
+ switch (type) {
+ case DesktopLocation:
+ qWarning("No desktop concept in Symbian OS");
+ // But lets still use some feasible default
+ path.Append(writableDataRoot());
+ break;
+ case DocumentsLocation:
+ path.Append(writableDataRoot());
+ break;
+ case FontsLocation:
+ path.Append(KFontsDir);
+ break;
+ case ApplicationsLocation:
+ path.Append(exeDrive().Name());
+ path.Append(KSysBin);
+ break;
+ case MusicLocation:
+ path.Append(writableDataRoot());
+ path.Append(PathInfo::SoundsPath());
+ break;
+ case MoviesLocation:
+ path.Append(writableDataRoot());
+ path.Append(PathInfo::VideosPath());
+ break;
+ case PicturesLocation:
+ path.Append(writableDataRoot());
+ path.Append(PathInfo::ImagesPath());
+ break;
+ case TempLocation:
+ return QDir::tempPath();
+ break;
+ case HomeLocation:
+ path.Append(writableDataRoot());
+ //return QDir::homePath(); break;
+ break;
+ case DataLocation:
+ qt_s60GetRFs().PrivatePath(path);
+ path.Insert(0, writableExeDrive().Name());
+ break;
+ case CacheLocation:
+ qt_s60GetRFs().PrivatePath(path);
+ path.Insert(0, writableExeDrive().Name());
+ path.Append(KCacheSubDir);
+ break;
+ default:
+ // Lets use feasible default
+ path.Append(writableDataRoot());
+ break;
+ }
+
+ // Convert to cross-platform format and clean the path
+ QString nativePath = QString::fromUtf16(path.Ptr(), path.Length());
+ QString qtPath = QDir::fromNativeSeparators(nativePath);
+ qtPath = QDir::cleanPath(qtPath);
+
+ // Note: The storage location returned can be a directory that does not exist;
+ // i.e., it may need to be created by the system or the user.
+ return qtPath;
+}
+
+typedef QString (*LocalizerFunc)(QString&);
+
+static QString defaultLocalizedDirectoryName(QString&)
+{
+ return QString();
+}
+
+QString QDesktopServices::displayName(StandardLocation type)
+{
+ static LocalizerFunc ptrLocalizerFunc = NULL;
+
+ if (!ptrLocalizerFunc) {
+ ptrLocalizerFunc = reinterpret_cast<LocalizerFunc>
+ (qt_resolveS60PluginFunc(S60Plugin_LocalizedDirectoryName));
+ if (!ptrLocalizerFunc)
+ ptrLocalizerFunc = &defaultLocalizedDirectoryName;
+ }
+
+ QString rawPath = storageLocation(type);
+ return ptrLocalizerFunc(rawPath);
+}
+
+
+QT_END_NAMESPACE
diff --git a/src/gui/util/qdesktopservices_win.cpp b/src/gui/util/qdesktopservices_win.cpp
new file mode 100644
index 0000000000..783970ba82
--- /dev/null
+++ b/src/gui/util/qdesktopservices_win.cpp
@@ -0,0 +1,267 @@
+/****************************************************************************
+**
+** 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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <qsettings.h>
+#include <qdir.h>
+#include <private/qsystemlibrary_p.h>
+#include <qurl.h>
+#include <qstringlist.h>
+#include <qprocess.h>
+#include <qtemporaryfile.h>
+#include <qcoreapplication.h>
+
+#include <qt_windows.h>
+#include <shlobj.h>
+#if !defined(Q_OS_WINCE)
+# include <intshcut.h>
+#else
+# include <qguifunctions_wince.h>
+# if !defined(STANDARDSHELL_UI_MODEL)
+# include <winx.h>
+# endif
+#endif
+
+#ifndef CSIDL_MYMUSIC
+#define CSIDL_MYMUSIC 13
+#define CSIDL_MYVIDEO 14
+#endif
+
+#ifndef QT_NO_DESKTOPSERVICES
+
+QT_BEGIN_NAMESPACE
+
+static bool openDocument(const QUrl &file)
+{
+ if (!file.isValid())
+ return false;
+ QString filePath = file.toLocalFile();
+ if (filePath.isEmpty())
+ filePath = file.toString();
+ quintptr returnValue = (quintptr)ShellExecute(0, 0, (wchar_t*)filePath.utf16(), 0, 0, SW_SHOWNORMAL);
+ return (returnValue > 32); //ShellExecute returns a value greater than 32 if successful
+}
+
+static QString expandEnvStrings(const QString &command)
+{
+#if defined(Q_OS_WINCE)
+ return command;
+#else
+ wchar_t buffer[MAX_PATH];
+ if (ExpandEnvironmentStrings((wchar_t*)command.utf16(), buffer, MAX_PATH))
+ return QString::fromWCharArray(buffer);
+ else
+ return command;
+#endif
+}
+
+static bool launchWebBrowser(const QUrl &url)
+{
+ if (url.scheme() == QLatin1String("mailto")) {
+ //Retrieve the commandline for the default mail client
+ //the default key used below is the command line for the mailto: shell command
+ DWORD bufferSize = sizeof(wchar_t) * MAX_PATH;
+ long returnValue = -1;
+ QString command;
+
+ HKEY handle;
+ LONG res;
+ wchar_t keyValue[MAX_PATH] = {0};
+ QString keyName(QLatin1String("mailto"));
+
+ //Check if user has set preference, otherwise use default.
+ res = RegOpenKeyEx(HKEY_CURRENT_USER,
+ L"Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\mailto\\UserChoice",
+ 0, KEY_READ, &handle);
+ if (res == ERROR_SUCCESS) {
+ returnValue = RegQueryValueEx(handle, L"Progid", 0, 0, reinterpret_cast<unsigned char*>(keyValue), &bufferSize);
+ if (!returnValue)
+ keyName = QString::fromUtf16((const ushort*)keyValue);
+ RegCloseKey(handle);
+ }
+ keyName += QLatin1String("\\Shell\\Open\\Command");
+ res = RegOpenKeyExW(HKEY_CLASSES_ROOT, (const wchar_t*)keyName.utf16(), 0, KEY_READ, &handle);
+ if (res != ERROR_SUCCESS)
+ return false;
+
+ bufferSize = sizeof(wchar_t) * MAX_PATH;
+ returnValue = RegQueryValueEx(handle, L"", 0, 0, reinterpret_cast<unsigned char*>(keyValue), &bufferSize);
+ if (!returnValue)
+ command = QString::fromRawData((QChar*)keyValue, bufferSize);
+ RegCloseKey(handle);
+
+ if (returnValue)
+ return false;
+
+ command = expandEnvStrings(command);
+ command = command.trimmed();
+ //Make sure the path for the process is in quotes
+ int index = -1 ;
+ if (command[0]!= QLatin1Char('\"')) {
+ index = command.indexOf(QLatin1String(".exe "), 0, Qt::CaseInsensitive);
+ command.insert(index+4, QLatin1Char('\"'));
+ command.insert(0, QLatin1Char('\"'));
+ }
+ //pass the url as the parameter
+ index = command.lastIndexOf(QLatin1String("%1"));
+ if (index != -1){
+ command.replace(index, 2, url.toString());
+ }
+ //start the process
+ PROCESS_INFORMATION pi;
+ ZeroMemory(&pi, sizeof(pi));
+ STARTUPINFO si;
+ ZeroMemory(&si, sizeof(si));
+ si.cb = sizeof(si);
+
+ returnValue = CreateProcess(NULL, (wchar_t*)command.utf16(), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
+
+ if (!returnValue)
+ return false;
+
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ return true;
+ }
+
+ if (!url.isValid())
+ return false;
+
+ if (url.scheme().isEmpty())
+ return openDocument(url);
+
+ quintptr returnValue = (quintptr)ShellExecute(0, 0, (wchar_t *)QString::fromUtf8(url.toEncoded().constData()).utf16(),
+ 0, 0, SW_SHOWNORMAL);
+ return (returnValue > 32);
+}
+
+QString QDesktopServices::storageLocation(StandardLocation type)
+{
+ QString result;
+
+#ifndef Q_OS_WINCE
+ QSystemLibrary library(QLatin1String("shell32"));
+#else
+ QSystemLibrary library(QLatin1String("coredll"));
+#endif // Q_OS_WINCE
+ typedef BOOL (WINAPI*GetSpecialFolderPath)(HWND, LPWSTR, int, BOOL);
+ static GetSpecialFolderPath SHGetSpecialFolderPath =
+ (GetSpecialFolderPath)library.resolve("SHGetSpecialFolderPathW");
+ if (!SHGetSpecialFolderPath)
+ return QString();
+
+ wchar_t path[MAX_PATH];
+
+ switch (type) {
+ case DataLocation:
+#if defined Q_WS_WINCE
+ if (SHGetSpecialFolderPath(0, path, CSIDL_APPDATA, FALSE))
+#else
+ if (SHGetSpecialFolderPath(0, path, CSIDL_LOCAL_APPDATA, FALSE))
+#endif
+ result = QString::fromWCharArray(path);
+ if (!QCoreApplication::organizationName().isEmpty())
+ result = result + QLatin1String("\\") + QCoreApplication::organizationName();
+ if (!QCoreApplication::applicationName().isEmpty())
+ result = result + QLatin1String("\\") + QCoreApplication::applicationName();
+ break;
+
+ case DesktopLocation:
+ if (SHGetSpecialFolderPath(0, path, CSIDL_DESKTOPDIRECTORY, FALSE))
+ result = QString::fromWCharArray(path);
+ break;
+
+ case DocumentsLocation:
+ if (SHGetSpecialFolderPath(0, path, CSIDL_PERSONAL, FALSE))
+ result = QString::fromWCharArray(path);
+ break;
+
+ case FontsLocation:
+ if (SHGetSpecialFolderPath(0, path, CSIDL_FONTS, FALSE))
+ result = QString::fromWCharArray(path);
+ break;
+
+ case ApplicationsLocation:
+ if (SHGetSpecialFolderPath(0, path, CSIDL_PROGRAMS, FALSE))
+ result = QString::fromWCharArray(path);
+ break;
+
+ case MusicLocation:
+ if (SHGetSpecialFolderPath(0, path, CSIDL_MYMUSIC, FALSE))
+ result = QString::fromWCharArray(path);
+ break;
+
+ case MoviesLocation:
+ if (SHGetSpecialFolderPath(0, path, CSIDL_MYVIDEO, FALSE))
+ result = QString::fromWCharArray(path);
+ break;
+
+ case PicturesLocation:
+ if (SHGetSpecialFolderPath(0, path, CSIDL_MYPICTURES, FALSE))
+ result = QString::fromWCharArray(path);
+ break;
+
+ case CacheLocation:
+ // Although Microsoft has a Cache key it is a pointer to IE's cache, not a cache
+ // location for everyone. Most applications seem to be using a
+ // cache directory located in their AppData directory
+ return storageLocation(DataLocation) + QLatin1String("\\cache");
+
+ case QDesktopServices::HomeLocation:
+ return QDir::homePath(); break;
+
+ case QDesktopServices::TempLocation:
+ return QDir::tempPath(); break;
+
+ default:
+ break;
+ }
+ return result;
+}
+
+QString QDesktopServices::displayName(StandardLocation type)
+{
+ Q_UNUSED(type);
+ return QString();
+}
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_DESKTOPSERVICES
diff --git a/src/gui/util/qdesktopservices_x11.cpp b/src/gui/util/qdesktopservices_x11.cpp
new file mode 100644
index 0000000000..e685bed0b9
--- /dev/null
+++ b/src/gui/util/qdesktopservices_x11.cpp
@@ -0,0 +1,242 @@
+/****************************************************************************
+**
+** 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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qdesktopservices.h"
+
+#ifndef QT_NO_DESKTOPSERVICES
+
+#include <qprocess.h>
+#include <qurl.h>
+#include <qdir.h>
+#include <qfile.h>
+#include <qtextstream.h>
+#include <private/qt_x11_p.h>
+#include <qcoreapplication.h>
+#include <stdlib.h>
+
+QT_BEGIN_NAMESPACE
+
+inline static bool launch(const QUrl &url, const QString &client)
+{
+#if !defined(QT_NO_PROCESS)
+ return (QProcess::startDetached(client + QLatin1Char(' ') + QString::fromLatin1(url.toEncoded().constData())));
+#else
+ return (::system((client + QLatin1Char(' ') + QString::fromLatin1(url.toEncoded().constData())).toLocal8Bit().constData()) != -1);
+#endif
+}
+
+static bool openDocument(const QUrl &url)
+{
+ if (!url.isValid())
+ return false;
+
+ if (launch(url, QLatin1String("xdg-open")))
+ return true;
+
+ // Use the X11->desktopEnvironment value if X11 is non-NULL,
+ // otherwise just attempt to launch command regardless of the desktop environment
+ if ((!X11 || (X11 && X11->desktopEnvironment == DE_GNOME)) && launch(url, QLatin1String("gnome-open"))) {
+ return true;
+ } else {
+ if ((!X11 || (X11 && X11->desktopEnvironment == DE_KDE)) && launch(url, QLatin1String("kfmclient exec")))
+ return true;
+ }
+
+ if (launch(url, QLatin1String("firefox")))
+ return true;
+ if (launch(url, QLatin1String("mozilla")))
+ return true;
+ if (launch(url, QLatin1String("netscape")))
+ return true;
+ if (launch(url, QLatin1String("opera")))
+ return true;
+
+ return false;
+}
+
+static bool launchWebBrowser(const QUrl &url)
+{
+ if (!url.isValid())
+ return false;
+ if (url.scheme() == QLatin1String("mailto"))
+ return openDocument(url);
+
+ if (launch(url, QLatin1String("xdg-open")))
+ return true;
+ if (launch(url, QString::fromLocal8Bit(getenv("DEFAULT_BROWSER"))))
+ return true;
+ if (launch(url, QString::fromLocal8Bit(getenv("BROWSER"))))
+ return true;
+
+ // Use the X11->desktopEnvironment value if X11 is non-NULL,
+ // otherwise just attempt to launch command regardless of the desktop environment
+ if ((!X11 || (X11 && X11->desktopEnvironment == DE_GNOME)) && launch(url, QLatin1String("gnome-open"))) {
+ return true;
+ } else {
+ if ((!X11 || (X11 && X11->desktopEnvironment == DE_KDE)) && launch(url, QLatin1String("kfmclient openURL")))
+ return true;
+ }
+
+ if (launch(url, QLatin1String("firefox")))
+ return true;
+ if (launch(url, QLatin1String("mozilla")))
+ return true;
+ if (launch(url, QLatin1String("netscape")))
+ return true;
+ if (launch(url, QLatin1String("opera")))
+ return true;
+ return false;
+}
+
+
+
+QString QDesktopServices::storageLocation(StandardLocation type)
+{
+ if (type == QDesktopServices::HomeLocation)
+ return QDir::homePath();
+ if (type == QDesktopServices::TempLocation)
+ return QDir::tempPath();
+
+ // http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html
+ if (type == QDesktopServices::CacheLocation) {
+ QString xdgCacheHome = QLatin1String(qgetenv("XDG_CACHE_HOME"));
+ if (xdgCacheHome.isEmpty())
+ xdgCacheHome = QDir::homePath() + QLatin1String("/.cache");
+ xdgCacheHome += QLatin1Char('/') + QCoreApplication::organizationName()
+ + QLatin1Char('/') + QCoreApplication::applicationName();
+ return xdgCacheHome;
+ }
+
+ if (type == QDesktopServices::DataLocation) {
+ QString xdgDataHome = QLatin1String(qgetenv("XDG_DATA_HOME"));
+ if (xdgDataHome.isEmpty())
+ xdgDataHome = QDir::homePath() + QLatin1String("/.local/share");
+ xdgDataHome += QLatin1String("/data/")
+ + QCoreApplication::organizationName() + QLatin1Char('/')
+ + QCoreApplication::applicationName();
+ return xdgDataHome;
+ }
+
+ // http://www.freedesktop.org/wiki/Software/xdg-user-dirs
+ QString xdgConfigHome = QLatin1String(qgetenv("XDG_CONFIG_HOME"));
+ if (xdgConfigHome.isEmpty())
+ xdgConfigHome = QDir::homePath() + QLatin1String("/.config");
+ QFile file(xdgConfigHome + QLatin1String("/user-dirs.dirs"));
+ if (file.exists() && file.open(QIODevice::ReadOnly)) {
+ QHash<QString, QString> lines;
+ QTextStream stream(&file);
+ // Only look for lines like: XDG_DESKTOP_DIR="$HOME/Desktop"
+ QRegExp exp(QLatin1String("^XDG_(.*)_DIR=(.*)$"));
+ while (!stream.atEnd()) {
+ QString line = stream.readLine();
+ if (exp.indexIn(line) != -1) {
+ QStringList lst = exp.capturedTexts();
+ QString key = lst.at(1);
+ QString value = lst.at(2);
+ if (value.length() > 2
+ && value.startsWith(QLatin1Char('\"'))
+ && value.endsWith(QLatin1Char('\"')))
+ value = value.mid(1, value.length() - 2);
+ // Store the key and value: "DESKTOP", "$HOME/Desktop"
+ lines[key] = value;
+ }
+ }
+
+ QString key;
+ switch (type) {
+ case DesktopLocation: key = QLatin1String("DESKTOP"); break;
+ case DocumentsLocation: key = QLatin1String("DOCUMENTS"); break;
+ case PicturesLocation: key = QLatin1String("PICTURES"); break;
+ case MusicLocation: key = QLatin1String("MUSIC"); break;
+ case MoviesLocation: key = QLatin1String("VIDEOS"); break;
+ default: break;
+ }
+ if (!key.isEmpty() && lines.contains(key)) {
+ QString value = lines[key];
+ // value can start with $HOME
+ if (value.startsWith(QLatin1String("$HOME")))
+ value = QDir::homePath() + value.mid(5);
+ return value;
+ }
+ }
+
+ QDir emptyDir;
+ QString path;
+ switch (type) {
+ case DesktopLocation:
+ path = QDir::homePath() + QLatin1String("/Desktop");
+ break;
+ case DocumentsLocation:
+ path = QDir::homePath() + QLatin1String("/Documents");
+ break;
+ case PicturesLocation:
+ path = QDir::homePath() + QLatin1String("/Pictures");
+ break;
+
+ case FontsLocation:
+ path = QDir::homePath() + QLatin1String("/.fonts");
+ break;
+
+ case MusicLocation:
+ path = QDir::homePath() + QLatin1String("/Music");
+ break;
+
+ case MoviesLocation:
+ path = QDir::homePath() + QLatin1String("/Videos");
+ break;
+
+ case ApplicationsLocation:
+ default:
+ break;
+ }
+
+ return path;
+}
+
+QString QDesktopServices::displayName(StandardLocation type)
+{
+ Q_UNUSED(type);
+ return QString();
+}
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_DESKTOPSERVICES
diff --git a/src/gui/util/qflickgesture.cpp b/src/gui/util/qflickgesture.cpp
new file mode 100644
index 0000000000..fdd2a95333
--- /dev/null
+++ b/src/gui/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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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
+ Qt::MouseButtons mouseButtons = QApplication::mouseButtons();
+
+ // release all pressed mouse buttons
+ /*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/gui/util/qflickgesture_p.h b/src/gui/util/qflickgesture_p.h
new file mode 100644
index 0000000000..451b579edd
--- /dev/null
+++ b/src/gui/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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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_GUI_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/gui/util/qscroller.cpp b/src/gui/util/qscroller.cpp
new file mode 100644
index 0000000000..db128c136a
--- /dev/null
+++ b/src/gui/util/qscroller.cpp
@@ -0,0 +1,2059 @@
+/****************************************************************************
+**
+** 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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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)
+{
+ Q_Q(QScroller);
+ QPointF ppm = q->pixelPerMeter();
+ const QScrollerPropertiesPrivate *sp = properties.d.data();
+ QPointF v = q->velocity();
+
+ 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/gui/util/qscroller.h b/src/gui/util/qscroller.h
new file mode 100644
index 0000000000..1599c7dcdf
--- /dev/null
+++ b/src/gui/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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSCROLLER_H
+#define QSCROLLER_H
+
+#include <QtCore/QObject>
+#include <QtCore/QPointF>
+#include <QtGui/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_GUI_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/gui/util/qscroller_mac.mm b/src/gui/util/qscroller_mac.mm
new file mode 100644
index 0000000000..4bf69c17c0
--- /dev/null
+++ b/src/gui/util/qscroller_mac.mm
@@ -0,0 +1,71 @@
+/****************************************************************************
+**
+** 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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore/qglobal.h>
+
+#ifdef Q_WS_MAC
+
+#import <Cocoa/Cocoa.h>
+
+#include "qscroller_p.h"
+
+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];
+}
+
+#endif
diff --git a/src/gui/util/qscroller_p.h b/src/gui/util/qscroller_p.h
new file mode 100644
index 0000000000..c1196152df
--- /dev/null
+++ b/src/gui/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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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/gui/util/qscrollerproperties.cpp b/src/gui/util/qscrollerproperties.cpp
new file mode 100644
index 0000000000..85e2e82af1
--- /dev/null
+++ b/src/gui/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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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/gui/util/qscrollerproperties.h b/src/gui/util/qscrollerproperties.h
new file mode 100644
index 0000000000..75d8932f81
--- /dev/null
+++ b/src/gui/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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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_GUI_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/gui/util/qscrollerproperties_p.h b/src/gui/util/qscrollerproperties_p.h
new file mode 100644
index 0000000000..76d8b0a79b
--- /dev/null
+++ b/src/gui/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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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/gui/util/qsystemtrayicon.cpp b/src/gui/util/qsystemtrayicon.cpp
new file mode 100644
index 0000000000..33cc61ee97
--- /dev/null
+++ b/src/gui/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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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 (QTextControl *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/gui/util/qsystemtrayicon.h b/src/gui/util/qsystemtrayicon.h
new file mode 100644
index 0000000000..1f0dab5393
--- /dev/null
+++ b/src/gui/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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSYSTEMTRAYICON_H
+#define QSYSTEMTRAYICON_H
+
+#include <QtCore/qobject.h>
+
+#ifndef QT_NO_SYSTEMTRAYICON
+
+#include <QtGui/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_GUI_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/gui/util/qsystemtrayicon_mac.mm b/src/gui/util/qsystemtrayicon_mac.mm
new file mode 100644
index 0000000000..0ec0b07040
--- /dev/null
+++ b/src/gui/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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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/gui/util/qsystemtrayicon_p.h b/src/gui/util/qsystemtrayicon_p.h
new file mode 100644
index 0000000000..cc7bd47092
--- /dev/null
+++ b/src/gui/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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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 "QtGui/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/gui/util/qsystemtrayicon_qws.cpp b/src/gui/util/qsystemtrayicon_qws.cpp
new file mode 100644
index 0000000000..48f8fe8922
--- /dev/null
+++ b/src/gui/util/qsystemtrayicon_qws.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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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/gui/util/qsystemtrayicon_win.cpp b/src/gui/util/qsystemtrayicon_win.cpp
new file mode 100644
index 0000000000..5a0e179e6d
--- /dev/null
+++ b/src/gui/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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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/gui/util/qsystemtrayicon_wince.cpp b/src/gui/util/qsystemtrayicon_wince.cpp
new file mode 100644
index 0000000000..85a34bbe25
--- /dev/null
+++ b/src/gui/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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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/gui/util/qsystemtrayicon_x11.cpp b/src/gui/util/qsystemtrayicon_x11.cpp
new file mode 100644
index 0000000000..694746c834
--- /dev/null
+++ b/src/gui/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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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/gui/util/qundogroup.cpp b/src/gui/util/qundogroup.cpp
new file mode 100644
index 0000000000..42cda7462a
--- /dev/null
+++ b/src/gui/util/qundogroup.cpp
@@ -0,0 +1,499 @@
+/****************************************************************************
+**
+** 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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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 prefix "Undo" is used.
+
+ \sa createRedoAction() canUndo() QUndoCommand::text()
+*/
+
+QAction *QUndoGroup::createUndoAction(QObject *parent, const QString &prefix) const
+{
+ QString pref = prefix.isEmpty() ? tr("Undo") : prefix;
+ QUndoAction *result = new QUndoAction(pref, parent);
+ 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 prefix "Undo" is used.
+
+ \sa createUndoAction() canRedo() QUndoCommand::text()
+*/
+
+QAction *QUndoGroup::createRedoAction(QObject *parent, const QString &prefix) const
+{
+ QString pref = prefix.isEmpty() ? tr("Redo") : prefix;
+ QUndoAction *result = new QUndoAction(pref, parent);
+ 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/gui/util/qundogroup.h b/src/gui/util/qundogroup.h
new file mode 100644
index 0000000000..4ed79a9c81
--- /dev/null
+++ b/src/gui/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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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_GUI_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/gui/util/qundostack.cpp b/src/gui/util/qundostack.cpp
new file mode 100644
index 0000000000..6b038ee52e
--- /dev/null
+++ b/src/gui/util/qundostack.cpp
@@ -0,0 +1,1127 @@
+/****************************************************************************
+**
+** 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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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);
+ d->text = 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 when the text properties of the stack's undo and redo
+ actions are updated.
+
+ \sa setText(), QUndoStack::createUndoAction(), QUndoStack::createRedoAction()
+*/
+
+QString QUndoCommand::text() const
+{
+ return d->text;
+}
+
+/*!
+ 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.
+
+ \sa text() QUndoStack::createUndoAction() QUndoStack::createRedoAction()
+*/
+
+void QUndoCommand::setText(const QString &text)
+{
+ d->text = 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)
+{
+ QString s = m_prefix;
+ if (!m_prefix.isEmpty() && !text.isEmpty())
+ s.append(QLatin1Char(' '));
+ s.append(text);
+ setText(s);
+}
+
+#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::text() 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)->text();
+ return QString();
+}
+
+/*!
+ Returns the text of the command which will be redone in the next call to redo().
+
+ \sa QUndoCommand::text() 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)->text();
+ 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 prefix "Undo" is used.
+
+ \sa createRedoAction(), canUndo(), QUndoCommand::text()
+*/
+
+QAction *QUndoStack::createUndoAction(QObject *parent, const QString &prefix) const
+{
+ QString pref = prefix.isEmpty() ? tr("Undo") : prefix;
+ QUndoAction *result = new QUndoAction(pref, parent);
+ 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 prefix "Redo" is used.
+
+ \sa createUndoAction(), canRedo(), QUndoCommand::text()
+*/
+
+QAction *QUndoStack::createRedoAction(QObject *parent, const QString &prefix) const
+{
+ QString pref = prefix.isEmpty() ? tr("Redo") : prefix;
+ QUndoAction *result = new QUndoAction(pref, parent);
+ 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/gui/util/qundostack.h b/src/gui/util/qundostack.h
new file mode 100644
index 0000000000..65941b53c3
--- /dev/null
+++ b/src/gui/util/qundostack.h
@@ -0,0 +1,158 @@
+/****************************************************************************
+**
+** 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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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_GUI_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;
+ 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_GUI_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/gui/util/qundostack_p.h b/src/gui/util/qundostack_p.h
new file mode 100644
index 0000000000..3c7d0e7d85
--- /dev/null
+++ b/src/gui/util/qundostack_p.h
@@ -0,0 +1,111 @@
+/****************************************************************************
+**
+** 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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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 <QtGui/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;
+ 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);
+public Q_SLOTS:
+ void setPrefixedText(const QString &text);
+private:
+ QString m_prefix;
+};
+#endif // QT_NO_ACTION
+
+
+QT_END_NAMESPACE
+#endif // QT_NO_UNDOSTACK
+#endif // QUNDOSTACK_P_H
diff --git a/src/gui/util/qundoview.cpp b/src/gui/util/qundoview.cpp
new file mode 100644
index 0000000000..43c1774b25
--- /dev/null
+++ b/src/gui/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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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 <QtGui/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/gui/util/qundoview.h b/src/gui/util/qundoview.h
new file mode 100644
index 0000000000..de32a39c54
--- /dev/null
+++ b/src/gui/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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QUNDOVIEW_H
+#define QUNDOVIEW_H
+
+#include <QtGui/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_GUI_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/gui/util/util.pri b/src/gui/util/util.pri
new file mode 100644
index 0000000000..2814a2d30a
--- /dev/null
+++ b/src/gui/util/util.pri
@@ -0,0 +1,71 @@
+# Qt util module
+
+HEADERS += \
+ util/qsystemtrayicon.h \
+ util/qcompleter.h \
+ util/qcompleter_p.h \
+ util/qdesktopservices.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/qdesktopservices.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 {
+ SOURCES += \
+ util/qsystemtrayicon_win.cpp
+}
+
+unix:x11 {
+ SOURCES += \
+ util/qsystemtrayicon_x11.cpp
+}
+
+embedded|qpa {
+ SOURCES += \
+ util/qsystemtrayicon_qws.cpp
+}
+
+!embedded:!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
+}