diff options
Diffstat (limited to 'src/gui/util')
25 files changed, 9066 insertions, 0 deletions
diff --git a/src/gui/util/qcompleter.cpp b/src/gui/util/qcompleter.cpp new file mode 100644 index 0000000..998098c --- /dev/null +++ b/src/gui/util/qcompleter.cpp @@ -0,0 +1,1717 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \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 QDirModel 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) QDirModel. 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 QDirModel. + + 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 QDirModel. + + \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/qheaderview.h" +#include "QtGui/qlistview.h" +#include "QtGui/qapplication.h" +#include "QtGui/qevent.h" +#include "QtGui/qheaderview.h" +#include "QtGui/qdesktopwidget.h" + +QT_BEGIN_NAMESPACE + +QCompletionModel::QCompletionModel(QCompleterPrivate *c, QObject *parent) + : QAbstractProxyModel(*new QCompletionModelPrivate, parent), + c(c), engine(0), 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; + } + + delete engine; + if (sortedEngine) + engine = new QSortedModelEngine(c); + else + engine = new QUnsortedModelEngine(c); +} + +QModelIndex QCompletionModel::mapToSource(const QModelIndex& index) const +{ + Q_D(const QCompletionModel); + if (!index.isValid()) + return QModelIndex(); + + 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 dirModel = false; +#ifndef QT_NO_DIRMODEL + dirModel = (qobject_cast<QDirModel *>(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) + && (!dirModel || 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 ; + for (it1 = cache.begin(); it1 != cache.end(); ++it1) { + CacheItem& ci = it1.value(); + int sz = ci.count()/2; + QMap<QString, QMatchData>::iterator it2 = ci.begin(); + for (int i = 0; it2 != ci.end() && i < sz; i++, ++it2) { + cost -= it2.value().indices.cost(); + ci.erase(it2); + } + if (ci.count() == 0) + cache.erase(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), + sorting(QCompleter::UnsortedModel), wrap(true), eatFocusOut(true) +{ +} + +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()) + completion = prefix; + else { + 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 + } + + 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 rw, rh, w; + int h = (popup->sizeHintForRow(0) * qMin(7, 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 = rw = rect.width(); + pos = widget->mapToGlobal(dir == Qt::RightToLeft ? rect.bottomRight() : rect.bottomLeft()); + } else { + rh = widget->height(); + rw = widget->width(); + pos = widget->mapToGlobal(QPoint(0, widget->height() - 2)); + w = widget->width(); + } + + if ((pos.x() + rw) > (screen.x() + screen.width())) + pos.setX(screen.x() + screen.width() - w); + if (pos.x() < screen.x()) + pos.setX(screen.x()); + if (((pos.y() + rh) > (screen.y() + screen.height())) && ((pos.y() - h - rh) >= 0)) + pos.setY(pos.y() - qMax(h, popup->minimumHeight()) - rh + 2); + + popup->setGeometry(pos.x(), pos.y(), w, h); + + if (!popup->isVisible()) + popup->show(); +} + +/*! + 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 QDirModel, 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 +} + +/*! + 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); + popup->hide(); + popup->setParent(0, Qt::Popup); + popup->setFocusPolicy(Qt::NoFocus); + 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(popup, SIGNAL(clicked(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) { + 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, 0); + 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, 0); + 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); + 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 + 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::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. + + \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 + QDirModel. + + \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(); +#ifndef QT_NO_DIRMODEL + QDirModel *dirModel = qobject_cast<QDirModel *>(sourceModel); + if (!dirModel) +#endif + return sourceModel->data(index, d->role).toString(); + + QModelIndex idx = index; + QStringList list; + do { + QString t = sourceModel->data(idx, Qt::EditRole).toString(); + 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 QDirModel. + + 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; +#ifndef QT_NO_DIRMODEL + Q_D(const QCompleter); + isDirModel = qobject_cast<QDirModel *>(d->proxy->sourceModel()) != 0; +#endif + + if (!isDirModel || 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(QLatin1String("[") + QRegExp::escape(sep) + QLatin1String("]")); + 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 0000000..15df2b6 --- /dev/null +++ b/src/gui/util/qcompleter.h @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef 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(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 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()) +}; + +#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 0000000..88dc2c0 --- /dev/null +++ b/src/gui/util/qcompleter_p.h @@ -0,0 +1,262 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef 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; + QCompleter::ModelSorting sorting; + bool wrap; + + bool eatFocusOut; + QRect popupRect; + + void showPopup(const QRect&); + void _q_complete(QModelIndex, bool = false); + void _q_completionSelected(const QItemSelection&); + void _q_autoResizePopup(); + 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); + ~QCompletionModel() { delete engine; } + + 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; + 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 0000000..d7e77de --- /dev/null +++ b/src/gui/util/qdesktopservices.cpp @@ -0,0 +1,314 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdesktopservices.h" + +#ifndef QT_NO_DESKTOPSERVICES + +#include <qdebug.h> + +#if defined(Q_WS_QWS) +#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_WS_S60) +#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. + + 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(®istry->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(®istry->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, DataLocation and ApplicationsLocation always point to appropriate + folder on same drive with executable. FontsLocation always points to folder on ROM drive. + 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. + + \note On Mac OS X, DataLocation does not include QCoreApplication::organizationName. + Use code like this to add it: + + \code + QString location = QDesktopServices::storageLocation(QDesktopServices::DataLocation); + #ifdef Q_WS_MAC + location.insert(location.count() - QCoreApplication::applicationName().count(), + QCoreApplication::organizationName() + "/"); + #endif + \endcode +*/ + +/*! + \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 0000000..15774b4 --- /dev/null +++ b/src/gui/util/qdesktopservices.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef 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 0000000..5124068 --- /dev/null +++ b/src/gui/util/qdesktopservices_mac.cpp @@ -0,0 +1,182 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT_NO_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> + +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()) + 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 (QDesktopServices::HomeLocation == type) + return QDir::homePath(); + + short domain = kOnAppropriateDisk; + + if (QDesktopServices::DataLocation == type + || QDesktopServices::CacheLocation == type) + 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); + + QString appName = QCoreApplication::applicationName(); + if (!appName.isEmpty() && + (QDesktopServices::DataLocation == type || QDesktopServices::CacheLocation == type)) + path += QLatin1String("/") + appName; + + 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 0000000..f40010a --- /dev/null +++ b/src/gui/util/qdesktopservices_qws.cpp @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <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 0000000..a32a91c --- /dev/null +++ b/src/gui/util/qdesktopservices_s60.cpp @@ -0,0 +1,359 @@ +/**************************************************************************** +** +** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the $MODULE$ of the Qt Toolkit. +** +** $TROLLTECH_DUAL_EMBEDDED_LICENSE$ +** +****************************************************************************/ + +// This flag changes the implementation to use S60 CDcoumentHandler +// instead of apparch when opening the files +#undef USE_DOCUMENTHANDLER + +#include <qcoreapplication.h> +#include <qdir.h> +#include <qurl.h> +#include <private/qcore_symbian_p.h> + +#include <miutset.h> // KUidMsgTypeSMTP +#include <txtrich.h> // CRichText +#include <f32file.h> // TDriveUnit etc +#include <eikenv.h> // CEikonEnv +#include <apgcli.h> // RApaLsSession +#include <apgtask.h> // TApaTaskList, TApaTask +#include <sendui.h> // CSendUi +#include <cmessagedata.h> // CMessageData +#include <pathinfo.h> // PathInfo +#ifdef USE_DOCUMENTHANDLER +#include <documenthandler.h> // CDocumentHandler +#endif + +QT_BEGIN_NAMESPACE + +_LIT(KSysBin, "\\Sys\\Bin\\"); +_LIT(KTempDir, "\\System\\Temp\\"); +_LIT(KBrowserPrefix, "4 " ); +_LIT(KFontsDir, "z:\\resource\\Fonts\\"); +const TUid KUidBrowser = { 0x10008D39 }; + +static void handleMailtoSchemeL(const QUrl &url) +{ + QString recipient = url.path(); + QString subject = url.queryItemValue("subject"); + QString body = url.queryItemValue("body"); + QString to = url.queryItemValue("to"); + QString cc = url.queryItemValue("cc"); + QString bcc = url.queryItemValue("bcc"); + + // these fields might have comma separated addresses + QStringList recipients = recipient.split(","); + QStringList tos = to.split(","); + QStringList ccs = cc.split(","); + QStringList bccs = bcc.split(","); + + + CSendUi* sendUi = CSendUi::NewLC(); + + // Construct symbian sendUI data holder + CMessageData* messageData = CMessageData::NewLC(); + + // Subject + TPtrC subj( qt_QString2TPtrC(subject) ); + messageData->SetSubjectL( &subj ); + + // Body + CParaFormatLayer* paraFormat = CParaFormatLayer::NewL(); + CleanupStack::PushL( paraFormat ); + CCharFormatLayer* charFormat = CCharFormatLayer::NewL(); + CleanupStack::PushL( charFormat ); + CRichText* bodyRichText = CRichText::NewL( paraFormat, charFormat ); + CleanupStack::PushL( bodyRichText ); + + TPtrC bodyPtr( qt_QString2TPtrC(body) ); + if( bodyPtr.Length() ) + { + bodyRichText->InsertL( 0, bodyPtr ); + } + else + { + bodyRichText->InsertL( 0, KNullDesC ); + } + + messageData->SetBodyTextL( bodyRichText ); + + // To + foreach(QString item, recipients) + messageData->AppendToAddressL(qt_QString2TPtrC(item)); + + foreach(QString item, tos) + messageData->AppendToAddressL(qt_QString2TPtrC(item)); + + // Cc + foreach(QString item, ccs) + messageData->AppendCcAddressL(qt_QString2TPtrC(item)); + + // Bcc + foreach(QString item, bccs) + messageData->AppendBccAddressL(qt_QString2TPtrC(item)); + + sendUi->CreateAndSendMessageL( KUidMsgTypeSMTP, messageData ); + CleanupStack::PopAndDestroy( 5 ); // bodyRichText, charFormat, paraFormat, messageData, sendUi +} + +static bool handleMailtoScheme(const QUrl &url) +{ + TRAPD(err, handleMailtoSchemeL(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 + 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) +{ + TRAPD( err, handleOtherSchemesL(qt_QString2TPtrC(url.toEncoded()))); + return err ? false : true; +} + +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: + // TODO: Should we return drive root similar to MemoryCardRootPath + return PathInfo::PhoneMemoryRootPath(); + break; + } +} + +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 would support opening apps in embedded mode, + // but our Qt application window group seems to always get switched on top of embedded one + // -> Cannot use menus etc of embedded app -> used + + CDocumentHandler* docHandler = CDocumentHandler::NewLC(); + TDataType temp; + //Standalone file opening fails for some file-types at least in S60 3.1 emulator + //For example .txt file fails with KErrAlreadyInUse and music files with KERN-EXEC 0 + //Workaround is to use OpenFileEmbeddedL + //docHandler->OpenFileL(aUrl, temp); + + // Opening file with CDocumentHandler will leave if file does not exist + // Leave is trapped in openDocument and false returned to user. + docHandler->OpenFileEmbeddedL(aUrl, temp); + CleanupStack::PopAndDestroy(docHandler); +#endif +} + +#ifdef 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 bool handleUrl(const QUrl &url) +{ + if (!url.isValid()) + return false; + + TRAPD( err, handleUrlL(qt_QString2TPtrC(url.toString()))); + return err ? false : true; +} + +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 launchWebBrowser(const QUrl &url) +{ + return handleUrl(url); +} + +static bool openDocument(const QUrl &file) +{ + return handleUrl(url); +} +#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); + TRAPD(err, openDocumentL(qt_QString2TPtrC(filePath))); + return err ? false : true; +} + +QString QDesktopServices::storageLocation(StandardLocation type) +{ + TFileName path; + + switch (type) { + case DesktopLocation: + qWarning("QDesktopServices::storageLocation %d not implemented", type); + 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: + path.Append(writableExeDrive().Name()); + path.Append(KTempDir); + //return QDir::tempPath(); break; + break; + case HomeLocation: + path.Append(writableDataRoot()); + //return QDir::homePath(); break; + break; + case DataLocation: + CEikonEnv::Static()->FsSession().PrivatePath( path ); + // TODO: Should we actually return phone mem if data is on ROM? + path.Insert( 0, exeDrive().Name() ); + break; + default: + 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 0000000..0449cba --- /dev/null +++ b/src/gui/util/qdesktopservices_win.cpp @@ -0,0 +1,249 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qsettings.h> +#include <qdir.h> +#include <qurl.h> +#include <qstringlist.h> +#include <qprocess.h> +#include <qtemporaryfile.h> +#include <qcoreapplication.h> + +#include <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 QT_NO_DESKTOPSERVICES + +QT_BEGIN_NAMESPACE + +//#undef UNICODE + +static bool openDocument(const QUrl &file) +{ + if (!file.isValid()) + return false; + + quintptr returnValue; + QT_WA({ + returnValue = (quintptr)ShellExecute(0, 0, (TCHAR *)file.toString().utf16(), 0, 0, SW_SHOWNORMAL); + } , { + returnValue = (quintptr)ShellExecuteA(0, 0, file.toString().toLocal8Bit().constData(), 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 + QByteArray path = command.toLocal8Bit(); + char commandValue[2 * MAX_PATH] = {0}; + DWORD returnValue = ExpandEnvironmentStringsA(path.data(), commandValue, MAX_PATH); + if (returnValue) + return QString::fromLocal8Bit(commandValue); + else + return command; +#endif +} + +static bool launchWebBrowser(const QUrl &url) +{ + if (url.scheme() == QLatin1String("mailto")) { + //Retrieve the commandline for the default mail client + //the key used below is the command line for the mailto: shell command + DWORD bufferSize = 2 * MAX_PATH; + long returnValue = -1; + QString command; + + HKEY handle; + LONG res; + QT_WA ({ + res = RegOpenKeyExW(HKEY_CLASSES_ROOT, L"mailto\\Shell\\Open\\Command", 0, KEY_READ, &handle); + if (res != ERROR_SUCCESS) + return false; + + wchar_t keyValue[2 * MAX_PATH] = {0}; + returnValue = RegQueryValueExW(handle, L"", 0, 0, reinterpret_cast<unsigned char*>(keyValue), &bufferSize); + if (!returnValue) + command = QString::fromRawData((QChar*)keyValue, bufferSize); + }, { + res = RegOpenKeyExA(HKEY_CLASSES_ROOT, "mailto\\Shell\\Open\\Command", 0, KEY_READ, &handle); + if (res != ERROR_SUCCESS) + return false; + + char keyValue[2 * MAX_PATH] = {0}; + returnValue = RegQueryValueExA(handle, "", 0, 0, reinterpret_cast<unsigned char*>(keyValue), &bufferSize); + if (!returnValue) + command = QString::fromLocal8Bit(keyValue); + }); + 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)); + QT_WA ({ + STARTUPINFO si; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + + returnValue = CreateProcess(NULL, (TCHAR*)command.utf16(), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); + }, { + STARTUPINFOA si; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + + returnValue = CreateProcessA(NULL, command.toLocal8Bit().data(), 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; + + quintptr returnValue; + QT_WA ({ + returnValue = (quintptr)ShellExecute(0, 0, (TCHAR *) QString::fromUtf8(url.toEncoded().constData()).utf16(), 0, 0, SW_SHOWNORMAL); + } , { + returnValue = (quintptr)ShellExecuteA(0, 0, url.toEncoded().constData(), 0, 0, SW_SHOWNORMAL); + }); + return (returnValue > 32); +} + +QString QDesktopServices::storageLocation(StandardLocation type) +{ +#if !defined(QT_NO_SETTINGS) + QSettings settings(QSettings::UserScope, QLatin1String("Microsoft"), QLatin1String("Windows")); + settings.beginGroup(QLatin1String("CurrentVersion/Explorer/Shell Folders")); + switch (type) { + 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 DataLocation: + if (!settings.contains(QLatin1String("Local AppData"))) + break; + return settings.value(QLatin1String("Local AppData")).toString() + + QLatin1String("\\") + QCoreApplication::organizationName() + + QLatin1String("\\") + QCoreApplication::applicationName(); + break; + case DesktopLocation: + return settings.value(QLatin1String("Desktop")).toString(); + break; + + case DocumentsLocation: + return settings.value(QLatin1String("Personal")).toString(); + break; + + case FontsLocation: + return settings.value(QLatin1String("Fonts")).toString(); + break; + + case ApplicationsLocation: + return settings.value(QLatin1String("Programs")).toString(); + break; + + case MusicLocation: + return settings.value(QLatin1String("My Music")).toString(); + break; + + case MoviesLocation: + return settings.value(QLatin1String("My Video")).toString(); + break; + + case PicturesLocation: + return settings.value(QLatin1String("My Pictures")).toString(); + break; + + case QDesktopServices::HomeLocation: + return QDir::homePath(); break; + + case QDesktopServices::TempLocation: + return QDir::tempPath(); break; + + default: + break; + } +#endif + return QString(); +} + +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 0000000..b3486e8 --- /dev/null +++ b/src/gui/util/qdesktopservices_x11.cpp @@ -0,0 +1,234 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "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) +{ + return (QProcess::startDetached(client + QLatin1Char(' ') + QString::fromLatin1(url.toEncoded().constData()))); +} + +static bool openDocument(const QUrl &url) +{ + if (!url.isValid()) + return false; + + if (launch(url, QLatin1String("xdg-open"))) + return true; + + if (X11->desktopEnvironment == DE_GNOME && launch(url, QLatin1String("gnome-open"))) { + return true; + } else { + if (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; + + if (X11->desktopEnvironment == DE_GNOME && launch(url, QLatin1String("gnome-open"))) { + return true; + } else { + if (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(QLatin1String("\"")) + && value.endsWith(QLatin1String("\""))) + 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/qsystemtrayicon.cpp b/src/gui/util/qsystemtrayicon.cpp new file mode 100644 index 0000000..2e072c5 --- /dev/null +++ b/src/gui/util/qsystemtrayicon.cpp @@ -0,0 +1,675 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "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 application + \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() +{ +#if defined(Q_WS_QWS) + return false; +#endif + return true; +} + +/*! + \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. + + \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_OS_WINCE + f.setPointSize(f.pointSize() - 2); +#endif + titleLabel->setFont(f); + titleLabel->setTextFormat(Qt::PlainText); // to maintain compat with windows + +#ifdef Q_OS_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_OS_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_OS_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_OS_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 0000000..2845717 --- /dev/null +++ b/src/gui/util/qsystemtrayicon.h @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef 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 0000000..f6e858a --- /dev/null +++ b/src/gui/util/qsystemtrayicon_mac.mm @@ -0,0 +1,547 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** 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 + +@class QNSMenu; + +#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 +QT_END_NAMESPACE + +QT_USE_NAMESPACE + +@class QNSImageView; + +@interface QNSStatusItem : NSObject { + NSStatusItem *item; + QSystemTrayIcon *icon; + QSystemTrayIconPrivate *iconPrivate; + QNSImageView *imageCell; +} +-(id)initWithIcon:(QSystemTrayIcon*)icon iconPrivate:(QSystemTrayIconPrivate *)iprivate; +-(void)dealloc; +-(QSystemTrayIcon*)icon; +-(NSStatusItem*)item; +-(QRectF)geometry; +- (void)triggerSelector:(id)sender; +- (void)doubleClickSelector:(id)sender; +@end + +@interface QNSImageView : NSImageView { + BOOL down; + QNSStatusItem *parent; +} +-(id)initWithParent:(QNSStatusItem*)myParent; +-(QSystemTrayIcon*)icon; +-(void)menuTrackingDone:(NSNotification*)notification; +-(void)mousePressed:(NSEvent *)mouseEvent; +@end + +@interface QNSMenu : NSMenu { + QMenu *qmenu; +} +-(QMenu*)menu; +-(id)initWithQMenu:(QMenu*)qmenu; +-(void)menuNeedsUpdate:(QNSMenu*)menu; +-(void)selectedAction:(id)item; +@end + +QT_BEGIN_NAMESPACE +class QSystemTrayIconSys +{ +public: + QSystemTrayIconSys(QSystemTrayIcon *icon, QSystemTrayIconPrivate *d) { + QMacCocoaAutoReleasePool pool; + item = [[QNSStatusItem alloc] initWithIcon:icon iconPrivate:d]; + } + ~QSystemTrayIconSys() { + QMacCocoaAutoReleasePool pool; + [[[item item] view] setHidden: YES]; + [item release]; + } + 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; +} + +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(¬ificationIconFile, "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 QNSImageView +-(id)initWithParent:(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]->contextMenu()) + [self icon]->contextMenu()->hide(); + [self setNeedsDisplay:YES]; +} + +-(void)mousePressed:(NSEvent *)mouseEvent +{ + int clickCount = [mouseEvent clickCount]; + down = !down; + if(!down && [self icon]->contextMenu()) + [self icon]->contextMenu()->hide(); + [self setNeedsDisplay:YES]; + + if (down) + [parent triggerSelector:self]; + else if ((clickCount%2)) + [parent doubleClickSelector:self]; + while (down) { + mouseEvent = [[self window] nextEventMatchingMask:NSLeftMouseDownMask | NSLeftMouseUpMask + | NSLeftMouseDraggedMask | NSRightMouseDownMask | NSRightMouseUpMask + | NSRightMouseDraggedMask]; + switch ([mouseEvent type]) { + case NSRightMouseDown: + case NSRightMouseUp: + case NSLeftMouseDown: + case NSLeftMouseUp: + [self menuTrackingDone:nil]; + break; + case NSRightMouseDragged: + case NSLeftMouseDragged: + default: + /* Ignore any other kind of event. */ + break; + } + }; +} + +-(void)mouseDown:(NSEvent *)mouseEvent +{ + [self mousePressed:mouseEvent]; +} + +- (void)rightMouseDown:(NSEvent *)mouseEvent +{ + [self mousePressed:mouseEvent]; +} + + +-(void)drawRect:(NSRect)rect { + [[parent item] drawStatusBarBackgroundInRect:rect withHighlight:down]; + [super drawRect:rect]; +} +@end + +@implementation QNSStatusItem + +-(id)initWithIcon:(QSystemTrayIcon*)i iconPrivate:(QSystemTrayIconPrivate *)iPrivate +{ + self = [super init]; + if(self) { + icon = i; + iconPrivate = iPrivate; + item = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain]; + imageCell = [[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 { + Q_UNUSED(sender); + if(!icon) + return; + qtsystray_sendActivated(icon, QSystemTrayIcon::Trigger); + if (icon->contextMenu()) { +#if 0 + const QRectF geom = [self geometry]; + if(!geom.isNull()) { + [[NSNotificationCenter defaultCenter] addObserver:imageCell + selector:@selector(menuTrackingDone:) + name:nil + object:self]; + icon->contextMenu()->exec(geom.topLeft().toPoint(), 0); + [imageCell menuTrackingDone:nil]; + } else +#endif + { +#ifndef QT_MAC_USE_COCOA + [[[self item] view] removeAllToolTips]; + iconPrivate->updateToolTip_sys(); +#endif + NSMenu *m = [[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 QNSMenu +-(id)initWithQMenu:(QMenu*)qm { + self = [super init]; + if(self) { + self->qmenu = qm; + [self setDelegate:self]; + } + return self; +} +-(QMenu*)menu { + return qmenu; +} +-(void)menuNeedsUpdate:(QNSMenu*)menu { + 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()) { + const short scale = [[NSApp mainMenu] menuBarHeight]; + NSImage *nsimage = static_cast<NSImage *>(qt_mac_create_nsimage(icon.pixmap(QSize(scale, scale)))); + [item setImage: nsimage]; + [nsimage release]; + } + if(action->menu()) { + QNSMenu *sub = [[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 + + +/* Done here because this is the only .mm for now! -Sam */ +QMacCocoaAutoReleasePool::QMacCocoaAutoReleasePool() +{ + NSApplicationLoad(); + pool = (void*)[[NSAutoreleasePool alloc] init]; +} + +QMacCocoaAutoReleasePool::~QMacCocoaAutoReleasePool() +{ + [(NSAutoreleasePool*)pool release]; +} + diff --git a/src/gui/util/qsystemtrayicon_p.h b/src/gui/util/qsystemtrayicon_p.h new file mode 100644 index 0000000..8c0732d --- /dev/null +++ b/src/gui/util/qsystemtrayicon_p.h @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef 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(); + + QPointer<QMenu> menu; + QIcon icon; + QString toolTip; + QSystemTrayIconSys *sys; + bool visible; +}; + +class QBalloonTip : public QWidget +{ +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); + void wheelEvent(QWheelEvent *event); + 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 0000000..fc5fdbe --- /dev/null +++ b/src/gui/util/qsystemtrayicon_qws.cpp @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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; +} + +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 0000000..84f9de4 --- /dev/null +++ b/src/gui/util/qsystemtrayicon_win.cpp @@ -0,0 +1,748 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsystemtrayicon_p.h" +#ifndef QT_NO_SYSTEMTRAYICON +//#define _WIN32_IE 0x0500 +#define _WIN32_IE 0x0600 //required for NOTIFYICONDATAW_V2_SIZE + +//missing defines for MINGW : +#ifndef NIN_BALLOONTIMEOUT +#define NIN_BALLOONTIMEOUT (WM_USER + 4) +#endif +#ifndef NIN_BALLOONUSERCLICK +#define NIN_BALLOONUSERCLICK (WM_USER + 5) +#endif + +#include <qt_windows.h> +#include <commctrl.h> +#include <shlwapi.h> +#include <QBitmap> +#include <QLibrary> +#include <QApplication> +#include <QToolTip> +#include <QDesktopWidget> +#include <QSettings> + +#if defined(Q_OS_WINCE) && !defined(STANDARDSHELL_UI_MODEL) +# include <streams.h> +#endif + +QT_BEGIN_NAMESPACE + +#if defined(Q_OS_WINCE) +static const UINT q_uNOTIFYICONID = 13; // IDs from 0 to 12 are reserved on WinCE. +#else +static const UINT q_uNOTIFYICONID = 0; +#endif + +static uint MYWM_TASKBARCREATED = 0; +#define MYWM_NOTIFYICON (WM_APP+101) + +typedef BOOL (WINAPI *PtrShell_NotifyIcon)(DWORD,PNOTIFYICONDATA); +static PtrShell_NotifyIcon ptrShell_NotifyIcon = 0; + +static void resolveLibs() +{ + static bool triedResolve = false; +#if defined Q_OS_WINCE + QString libName(QLatin1String("coredll")); + const char* funcName = "Shell_NotifyIcon"; +#else + QString libName(QLatin1String("shell32")); + const char* funcName = "Shell_NotifyIconW"; +#endif + if (!triedResolve) { + QLibrary lib(libName); + triedResolve = true; + ptrShell_NotifyIcon = (PtrShell_NotifyIcon) lib.resolve(funcName); + } +} + +class QSystemTrayIconSys : QWidget +{ +public: + QSystemTrayIconSys(QSystemTrayIcon *object); + ~QSystemTrayIconSys(); + bool winEvent( MSG *m, long *result ); + bool trayMessageA(DWORD msg); + bool trayMessageW(DWORD msg); + bool trayMessage(DWORD msg); + bool iconDrawItem(LPDRAWITEMSTRUCT lpdi); + void setIconContentsW(NOTIFYICONDATAW &data); + void setIconContentsA(NOTIFYICONDATAA &data); + bool showMessageW(const QString &title, const QString &message, QSystemTrayIcon::MessageIcon type, uint uSecs); + bool showMessageA(const QString &title, const QString &message, QSystemTrayIcon::MessageIcon type, uint uSecs); + bool allowsMessages(); + bool supportsMessages(); + QRect findIconGeometry(const int a_iButtonID); + QRect findTrayGeometry(); + HBITMAP createIconMask(const QBitmap &bitmap); + void createIcon(); + int detectShellVersion() const; + HICON hIcon; + QPoint globalPos; + QSystemTrayIcon *q; +private: + uint notifyIconSizeW; + uint notifyIconSizeA; + int currentShellVersion; + int maxTipLength; +}; + +// Checks for the shell32 dll version number, since only version +// 5 or later of supports ballon messages +bool QSystemTrayIconSys::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 +} + +// Checks for the shell32 dll version number, since only version +// 5 or later of supports ballon messages +bool QSystemTrayIconSys::supportsMessages() +{ +#if NOTIFYICON_VERSION >= 3 + if (currentShellVersion >= 5) + return allowsMessages(); + else +#endif + return false; +} + +//Returns the runtime major version of the shell32 dll +int QSystemTrayIconSys::detectShellVersion() const +{ +#ifndef Q_OS_WINCE + int shellVersion = 4; //NT 4.0 and W95 + DLLGETVERSIONPROC pDllGetVersion = (DLLGETVERSIONPROC)QLibrary::resolve( + QLatin1String("shell32"), "DllGetVersion"); + if (pDllGetVersion) + { + DLLVERSIONINFO dvi; + HRESULT hr; + ZeroMemory(&dvi, sizeof(dvi)); + dvi.cbSize = sizeof(dvi); + hr = (*pDllGetVersion)(&dvi); + if (SUCCEEDED(hr)) { + if (dvi.dwMajorVersion >= 5) + { + shellVersion = dvi.dwMajorVersion; + } + } + } + return shellVersion; +#endif + return 4; //No ballonMessages and MaxTipLength = 64 for WindowsCE +} + +QSystemTrayIconSys::QSystemTrayIconSys(QSystemTrayIcon *object) + : hIcon(0), q(object) +{ + currentShellVersion = detectShellVersion(); + notifyIconSizeA = FIELD_OFFSET(NOTIFYICONDATAA, szTip[64]); // NOTIFYICONDATAA_V1_SIZE + notifyIconSizeW = FIELD_OFFSET(NOTIFYICONDATAW, szTip[64]); // NOTIFYICONDATAW_V1_SIZE; + maxTipLength = 64; + +#if NOTIFYICON_VERSION >= 3 + if (currentShellVersion >=5) { + notifyIconSizeA = FIELD_OFFSET(NOTIFYICONDATAA, guidItem); // NOTIFYICONDATAA_V2_SIZE + notifyIconSizeW = FIELD_OFFSET(NOTIFYICONDATAW, guidItem); // NOTIFYICONDATAW_V2_SIZE; + maxTipLength = 128; + } +#endif + + // For restoring the tray icon after explorer crashes + if (!MYWM_TASKBARCREATED) { + MYWM_TASKBARCREATED = QT_WA_INLINE(RegisterWindowMessageW(L"TaskbarCreated"),RegisterWindowMessageA("TaskbarCreated")); + } +} + +QSystemTrayIconSys::~QSystemTrayIconSys() +{ + if (hIcon) + DestroyIcon(hIcon); +} + +void QSystemTrayIconSys::setIconContentsW(NOTIFYICONDATAW &tnd) +{ + tnd.uFlags = NIF_MESSAGE|NIF_ICON|NIF_TIP; + tnd.uCallbackMessage = MYWM_NOTIFYICON; + tnd.hIcon = hIcon; + QString tip = q->toolTip(); + + if (!tip.isNull()) { + // Tip is limited to maxTipLength - NULL; lstrcpyn appends a NULL terminator. + tip = tip.left(maxTipLength - 1) + QChar(); +#if defined(Q_OS_WINCE) + wcsncpy(tnd.szTip, reinterpret_cast<const wchar_t *> (tip.utf16()), qMin(tip.length()+1, maxTipLength)); +#else + lstrcpynW(tnd.szTip, (TCHAR*)tip.utf16(), qMin(tip.length()+1, maxTipLength)); +#endif + } +} + +void QSystemTrayIconSys::setIconContentsA(NOTIFYICONDATAA &tnd) +{ + tnd.uFlags = NIF_MESSAGE|NIF_ICON|NIF_TIP; + tnd.uCallbackMessage = MYWM_NOTIFYICON; + tnd.hIcon = hIcon; + QString tip = q->toolTip(); + + if (!tip.isNull()) { + // Tip is limited to maxTipLength - NULL; lstrcpyn appends a NULL terminator. + tip = tip.left(maxTipLength - 1) + QChar(); +#if defined(Q_OS_WINCE) + strncpy(tnd.szTip, tip.toLocal8Bit().constData(), qMin(tip.length()+1, maxTipLength)); +#else + lstrcpynA(tnd.szTip, tip.toLocal8Bit().constData(), qMin(tip.length()+1, maxTipLength)); +#endif + } +} + +int iconFlag( QSystemTrayIcon::MessageIcon icon ) +{ + int flag = 0; +#if NOTIFYICON_VERSION >= 3 + switch (icon) { + case QSystemTrayIcon::NoIcon: + break; + case QSystemTrayIcon::Critical: + flag = NIIF_ERROR; + break; + case QSystemTrayIcon::Warning: + flag = NIIF_WARNING; + break; + case QSystemTrayIcon::Information: + default : // fall through + flag = NIIF_INFO; + } +#else + Q_UNUSED(icon); +#endif + return flag; +} + +bool QSystemTrayIconSys::showMessageW(const QString &title, const QString &message, QSystemTrayIcon::MessageIcon type, uint uSecs) +{ +#if NOTIFYICON_VERSION>=3 + NOTIFYICONDATA tnd; + memset(&tnd, 0, notifyIconSizeW); + Q_ASSERT(testAttribute(Qt::WA_WState_Created)); + + setIconContentsW(tnd); +#if defined(Q_OS_WINCE) + wcsncpy(tnd.szInfo, message.utf16(), qMin(message.length() + 1, 256)); + wcsncpy(tnd.szInfoTitle, title.utf16(), qMin(title.length()+1, 64)); +#else + lstrcpynW(tnd.szInfo, (TCHAR*)message.utf16(), qMin(message.length() + 1, 256)); + lstrcpynW(tnd.szInfoTitle, (TCHAR*)title.utf16(), qMin(title.length() + 1, 64)); +#endif + tnd.uID = q_uNOTIFYICONID; + tnd.dwInfoFlags = iconFlag(type); + tnd.cbSize = notifyIconSizeW; + tnd.hWnd = winId(); + tnd.uTimeout = uSecs; + tnd.uFlags = NIF_INFO; + return ptrShell_NotifyIcon(NIM_MODIFY, &tnd); +#else + Q_UNUSED(title); + Q_UNUSED(message); + Q_UNUSED(type); + Q_UNUSED(uSecs); + return false; +#endif +} + +bool QSystemTrayIconSys::showMessageA(const QString &title, const QString &message, QSystemTrayIcon::MessageIcon type, uint uSecs) +{ +#if NOTIFYICON_VERSION>=3 + NOTIFYICONDATAA tnd; + memset(&tnd, 0, notifyIconSizeA); + Q_ASSERT(testAttribute(Qt::WA_WState_Created)); + + setIconContentsA(tnd); +#if defined(Q_OS_WINCE) + strncpy(tnd.szInfo, message.toLocal8Bit().constData(), qMin(message.length() + 1, 256)); + strncpy(tnd.szInfoTitle, title.toLocal8Bit().constData(), qMin(title.length()+1, 64)); +#else + lstrcpynA(tnd.szInfo, message.toLocal8Bit().constData(), qMin(message.length() + 1, 256)); + lstrcpynA(tnd.szInfoTitle, title.toLocal8Bit().constData(), qMin(title.length() + 1, 64)); +#endif + tnd.uID = q_uNOTIFYICONID; + tnd.dwInfoFlags = iconFlag(type); + tnd.cbSize = notifyIconSizeA; + tnd.hWnd = winId(); + tnd.uTimeout = uSecs; + tnd.uFlags = NIF_INFO; + return Shell_NotifyIconA(NIM_MODIFY, &tnd); +#else + Q_UNUSED(title); + Q_UNUSED(message); + Q_UNUSED(type); + Q_UNUSED(uSecs); + return false; +#endif +} + +bool QSystemTrayIconSys::trayMessageA(DWORD msg) +{ +#if !defined(Q_OS_WINCE) + NOTIFYICONDATAA tnd; + memset(&tnd, 0, notifyIconSizeA); + tnd.uID = q_uNOTIFYICONID; + tnd.cbSize = notifyIconSizeA; + tnd.hWnd = winId(); + Q_ASSERT(testAttribute(Qt::WA_WState_Created)); + + if (msg != NIM_DELETE) { + setIconContentsA(tnd); + } + return Shell_NotifyIconA(msg, &tnd); +#else + Q_UNUSED(msg); + return false; +#endif +} + +bool QSystemTrayIconSys::trayMessageW(DWORD msg) +{ + NOTIFYICONDATAW tnd; + memset(&tnd, 0, notifyIconSizeW); + tnd.uID = q_uNOTIFYICONID; + tnd.cbSize = notifyIconSizeW; + tnd.hWnd = winId(); + Q_ASSERT(testAttribute(Qt::WA_WState_Created)); + + if (msg != NIM_DELETE) { + setIconContentsW(tnd); + } + return ptrShell_NotifyIcon(msg, &tnd); +} + +bool QSystemTrayIconSys::trayMessage(DWORD msg) +{ + resolveLibs(); + if (!(ptrShell_NotifyIcon)) + return false; + + QT_WA({ + return trayMessageW(msg); + }, + { + return trayMessageA(msg); + }); +} + +bool QSystemTrayIconSys::iconDrawItem(LPDRAWITEMSTRUCT lpdi) +{ + if (!hIcon) + return false; + + DrawIconEx(lpdi->hDC, lpdi->rcItem.left, lpdi->rcItem.top, hIcon, 0, 0, 0, 0, DI_NORMAL); + return true; +} + +HBITMAP QSystemTrayIconSys::createIconMask(const QBitmap &bitmap) +{ + QImage bm = bitmap.toImage().convertToFormat(QImage::Format_Mono); + int w = bm.width(); + int h = bm.height(); + int bpl = ((w+15)/16)*2; // bpl, 16 bit alignment + uchar *bits = new uchar[bpl*h]; + bm.invertPixels(); + for (int y=0; y<h; y++) + memcpy(bits+y*bpl, bm.scanLine(y), bpl); + HBITMAP hbm = CreateBitmap(w, h, 1, 1, bits); + delete [] bits; + return hbm; +} + +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; + + QBitmap mask = pm.mask(); + if (mask.isNull()) { + mask = QBitmap(pm.size()); + mask.fill(Qt::color1); + } + + HBITMAP im = createIconMask(mask); + ICONINFO ii; + ii.fIcon = true; + ii.hbmMask = im; + ii.hbmColor = pm.toWinHBITMAP(QPixmap::Alpha); + ii.xHotspot = 0; + ii.yHotspot = 0; + hIcon = CreateIconIndirect(&ii); + + DeleteObject(ii.hbmColor); + DeleteObject(im); +} + +bool QSystemTrayIconSys::winEvent( MSG *m, long *result ) +{ + switch(m->message) { + case WM_CREATE: +#ifdef GWLP_USERDATA + SetWindowLongPtr(winId(), GWLP_USERDATA, (LONG_PTR)((CREATESTRUCTW*)m->lParam)->lpCreateParams); +#else + SetWindowLong(winId(), GWL_USERDATA, (LONG)((CREATESTRUCTW*)m->lParam)->lpCreateParams); +#endif + break; + + case WM_DRAWITEM: + return iconDrawItem((LPDRAWITEMSTRUCT)m->lParam); + + case MYWM_NOTIFYICON: + { + RECT r; + GetWindowRect(winId(), &r); + QEvent *e = 0; + Qt::KeyboardModifiers keys = QApplication::keyboardModifiers(); + QPoint gpos = QCursor::pos(); + + switch (m->lParam) { + case WM_LBUTTONUP: + emit q->activated(QSystemTrayIcon::Trigger); + break; + +#if !defined(Q_OS_WINCE) + case WM_LBUTTONDBLCLK: + emit q->activated(QSystemTrayIcon::DoubleClick); + break; + + case WM_RBUTTONUP: + if (q->contextMenu()) { + q->contextMenu()->popup(gpos); + q->contextMenu()->activateWindow(); + //Must be activated for proper keyboardfocus and menu closing on windows: + } + emit q->activated(QSystemTrayIcon::Context); + break; + + case NIN_BALLOONUSERCLICK: + emit q->messageClicked(); + break; + + case WM_MBUTTONUP: + emit q->activated(QSystemTrayIcon::MiddleClick); + break; +#endif + default: + break; + } + if (e) { + bool res = QApplication::sendEvent(q, e); + delete e; + return res; + } + 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); + } +} + +//fallback on win 95/98 +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); +#if defined(Q_OS_WINCE) + HWND trayHandle = FindWindowW(L"Shell_TrayWnd", NULL); +#else + HWND trayHandle = FindWindowA("Shell_TrayWnd", NULL); +#endif + if (trayHandle) { +#if defined(Q_OS_WINCE) + trayHandle = FindWindowW(L"TrayNotifyWnd", NULL); +#else + trayHandle = FindWindowExA(trayHandle, NULL, "TrayNotifyWnd", NULL); +#endif + if (trayHandle) { + RECT r; + if (GetWindowRect(trayHandle, &r)) { + ret = QRect(r.left, r.top, r.right- r.left, r.bottom - r.top); + } + } + } + return ret; +} + +/* +* 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) +{ + QRect ret; + + TBBUTTON buttonData; + DWORD processID = 0; +#if defined(Q_OS_WINCE) + HWND trayHandle = FindWindowW(L"Shell_TrayWnd", NULL); +#else + HWND trayHandle = FindWindowA("Shell_TrayWnd", NULL); +#endif + + //find the toolbar used in the notification area + if (trayHandle) { +#if defined(Q_OS_WINCE) + trayHandle = FindWindowW(L"TrayNotifyWnd", NULL); +#else + trayHandle = FindWindowExA(trayHandle, NULL, "TrayNotifyWnd", NULL); +#endif + if (trayHandle) { +#if defined(Q_OS_WINCE) + HWND hwnd = FindWindowW(L"SysPager", NULL); +#else + HWND hwnd = FindWindowEx(trayHandle, NULL, L"SysPager", NULL); +#endif + if (hwnd) { +#if defined(Q_OS_WINCE) + hwnd = FindWindow(L"ToolbarWindow32", NULL); +#else + hwnd = FindWindowEx(hwnd, NULL, L"ToolbarWindow32", NULL); +#endif + 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); +#if defined(Q_OS_WINCE) + LPVOID data = VirtualAlloc(NULL, sizeof(TBBUTTON), MEM_COMMIT, PAGE_READWRITE); +#else + LPVOID data = VirtualAllocEx(trayProcess, NULL, sizeof(TBBUTTON), MEM_COMMIT, PAGE_READWRITE); +#endif + + 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; + } + } + } +#if defined(Q_OS_WINCE) + VirtualFree(data, 0, MEM_RELEASE); +#else + VirtualFreeEx(trayProcess, data, 0, MEM_RELEASE); +#endif + CloseHandle(trayProcess); + return ret; +} + + +void QSystemTrayIconPrivate::showMessage_sys(const QString &title, const QString &message, QSystemTrayIcon::MessageIcon type, int timeOut) +{ + if (!sys || !sys->allowsMessages()) + return; + + uint uSecs = 0; + if ( timeOut < 0) + uSecs = 10000; //10 sec default + else uSecs = (int)timeOut; + + resolveLibs(); + + //message is limited to 255 chars + NULL + QString messageString; + if (message.isEmpty() && !title.isEmpty()) + messageString = QLatin1String(" "); //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(); + + if (sys->supportsMessages()) { + QT_WA({ + sys->showMessageW(titleString, messageString, type, (unsigned int)uSecs); + }, { + sys->showMessageA(titleString, messageString, type, (unsigned int)uSecs); + }); + } else { + //use fallbacks + QRect iconPos = sys->findIconGeometry(0); + if (iconPos.isValid()) { + QBalloonTip::showBalloon(type, title, message, sys->q, iconPos.center(), uSecs, true); + } else { + QRect trayRect = sys->findTrayGeometry(); + QBalloonTip::showBalloon(type, title, message, sys->q, QPoint(trayRect.left(), + trayRect.center().y()), uSecs, false); + } + } +} + +QRect QSystemTrayIconPrivate::geometry_sys() const +{ + if (!sys) + return QRect(); + return sys->findIconGeometry(0); +} + +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() +{ +#ifdef Q_OS_WINCE + // 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(); +#else + if (!sys) + return; + + sys->trayMessage(NIM_MODIFY); +#endif +} + +bool QSystemTrayIconPrivate::isSystemTrayAvailable_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 0000000..52c258a --- /dev/null +++ b/src/gui/util/qsystemtrayicon_x11.cpp @@ -0,0 +1,394 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "private/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); +} + +void QSystemTrayIconSys::wheelEvent(QWheelEvent *e) +{ + QApplication::sendEvent(q, e); +} + +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; +} + +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 0000000..6fc05fe --- /dev/null +++ b/src/gui/util/qundogroup.cpp @@ -0,0 +1,500 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "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 + \ingroup misc + + 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 0000000..b2adb84 --- /dev/null +++ b/src/gui/util/qundogroup.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef 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 0000000..11f65e3 --- /dev/null +++ b/src/gui/util/qundostack.cpp @@ -0,0 +1,1129 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <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 + \ingroup misc + + 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 + \ingroup misc + + 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 initally 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 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 0000000..9fec136 --- /dev/null +++ b/src/gui/util/qundostack.h @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef 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 0000000..f1e1195 --- /dev/null +++ b/src/gui/util/qundostack_p.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef 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 0000000..6dbb2d4 --- /dev/null +++ b/src/gui/util/qundoview.cpp @@ -0,0 +1,476 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "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 misc + \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 0000000..b776ad3 --- /dev/null +++ b/src/gui/util/qundoview.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef 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 0000000..69c53ae --- /dev/null +++ b/src/gui/util/util.pri @@ -0,0 +1,45 @@ +# Qt util module + +HEADERS += \ + util/qsystemtrayicon.h \ + util/qcompleter.h \ + util/qcompleter_p.h \ + util/qdesktopservices.h \ + util/qsystemtrayicon_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/qundogroup.cpp \ + util/qundostack.cpp \ + util/qundoview.cpp + + +win32 { + SOURCES += \ + util/qsystemtrayicon_win.cpp +} + +unix:x11 { + SOURCES += \ + util/qsystemtrayicon_x11.cpp +} + +embedded { + SOURCES += \ + util/qsystemtrayicon_qws.cpp +} + +symbian { + # QDesktopServices uses CSendUi which is located on app layer + INCLUDEPATH += $$APP_LAYER_SYSTEMINCLUDE +} + +!embedded:!x11:mac { + OBJECTIVE_SOURCES += util/qsystemtrayicon_mac.mm +} |