blob: 47f96732ec71de0d569b177a43f25c6104d05247 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Assistant of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qhelpcontentwidget.h"
#include "qhelpenginecore.h"
#include "qhelpengine_p.h"
#include "qhelpcollectionhandler_p.h"
#include <QDir>
#include <QtCore/QStack>
#include <QtCore/QThread>
#include <QtCore/QMutex>
#include <QtWidgets/QHeaderView>
QT_BEGIN_NAMESPACE
class QHelpContentItemPrivate
{
public:
QHelpContentItemPrivate(const QString &t, const QUrl &l, QHelpContentItem *p)
: parent(p),
title(t),
link(l)
{
}
void appendChild(QHelpContentItem *item) { childItems.append(item); }
QList<QHelpContentItem*> childItems;
QHelpContentItem *parent;
QString title;
QUrl link;
};
class QHelpContentProvider : public QThread
{
Q_OBJECT
public:
QHelpContentProvider(QHelpEnginePrivate *helpEngine);
~QHelpContentProvider() override;
void collectContents(const QString &customFilterName);
void stopCollecting();
QHelpContentItem *takeContentItem();
private:
void run() override;
QHelpEnginePrivate *m_helpEngine;
QString m_currentFilter;
QStringList m_filterAttributes;
QString m_collectionFile;
QHelpContentItem *m_rootItem = nullptr;
QMutex m_mutex;
bool m_usesFilterEngine = false;
bool m_abort = false;
};
class QHelpContentModelPrivate
{
public:
QHelpContentItem *rootItem = nullptr;
QHelpContentProvider *qhelpContentProvider;
};
/*!
\class QHelpContentItem
\inmodule QtHelp
\brief The QHelpContentItem class provides an item for use with QHelpContentModel.
\since 4.4
*/
QHelpContentItem::QHelpContentItem(const QString &name, const QUrl &link, QHelpContentItem *parent)
{
d = new QHelpContentItemPrivate(name, link, parent);
}
/*!
Destroys the help content item.
*/
QHelpContentItem::~QHelpContentItem()
{
qDeleteAll(d->childItems);
delete d;
}
/*!
Returns the child of the content item in the give \a row.
\sa parent()
*/
QHelpContentItem *QHelpContentItem::child(int row) const
{
return d->childItems.value(row);
}
/*!
Returns the number of child items.
*/
int QHelpContentItem::childCount() const
{
return d->childItems.count();
}
/*!
Returns the row of this item from its parents view.
*/
int QHelpContentItem::row() const
{
if (d->parent)
return d->parent->d->childItems.indexOf(const_cast<QHelpContentItem*>(this));
return 0;
}
/*!
Returns the title of the content item.
*/
QString QHelpContentItem::title() const
{
return d->title;
}
/*!
Returns the URL of this content item.
*/
QUrl QHelpContentItem::url() const
{
return d->link;
}
/*!
Returns the parent content item.
*/
QHelpContentItem *QHelpContentItem::parent() const
{
return d->parent;
}
/*!
Returns the position of a given \a child.
*/
int QHelpContentItem::childPosition(QHelpContentItem *child) const
{
return d->childItems.indexOf(child);
}
QHelpContentProvider::QHelpContentProvider(QHelpEnginePrivate *helpEngine)
: QThread(helpEngine)
{
m_helpEngine = helpEngine;
}
QHelpContentProvider::~QHelpContentProvider()
{
stopCollecting();
}
void QHelpContentProvider::collectContents(const QString &customFilterName)
{
m_mutex.lock();
m_currentFilter = customFilterName;
m_filterAttributes = m_helpEngine->q->filterAttributes(customFilterName);
m_collectionFile = m_helpEngine->collectionHandler->collectionFile();
m_usesFilterEngine = m_helpEngine->usesFilterEngine;
m_mutex.unlock();
if (isRunning())
stopCollecting();
start(LowPriority);
}
void QHelpContentProvider::stopCollecting()
{
if (isRunning()) {
m_mutex.lock();
m_abort = true;
m_mutex.unlock();
wait();
// we need to force-set m_abort to false, because the thread might either have
// finished between the isRunning() check and the "m_abort = true" above, or the
// isRunning() check might already happen after the "m_abort = false" in the run() method,
// either way never resetting m_abort to false from within the run() method
m_abort = false;
}
delete m_rootItem;
m_rootItem = nullptr;
}
QHelpContentItem *QHelpContentProvider::takeContentItem()
{
QMutexLocker locker(&m_mutex);
QHelpContentItem *content = m_rootItem;
m_rootItem = nullptr;
return content;
}
// TODO: this is a copy from helpcollectionhandler, make it common
static QUrl buildQUrl(const QString &ns, const QString &folder,
const QString &relFileName, const QString &anchor)
{
QUrl url;
url.setScheme(QLatin1String("qthelp"));
url.setAuthority(ns);
url.setPath(QLatin1Char('/') + folder + QLatin1Char('/') + relFileName);
url.setFragment(anchor);
return url;
}
static QUrl constructUrl(const QString &namespaceName,
const QString &folderName,
const QString &relativePath)
{
const int idx = relativePath.indexOf(QLatin1Char('#'));
const QString &rp = idx < 0 ? relativePath : relativePath.left(idx);
const QString anchor = idx < 0 ? QString() : relativePath.mid(idx + 1);
return buildQUrl(namespaceName, folderName, rp, anchor);
}
void QHelpContentProvider::run()
{
QString title;
QString link;
int depth = 0;
QHelpContentItem *item = nullptr;
m_mutex.lock();
QHelpContentItem * const rootItem = new QHelpContentItem(QString(), QString(), nullptr);
const QString currentFilter = m_currentFilter;
const QStringList attributes = m_filterAttributes;
const QString collectionFile = m_collectionFile;
const bool usesFilterEngine = m_usesFilterEngine;
delete m_rootItem;
m_rootItem = nullptr;
m_mutex.unlock();
if (collectionFile.isEmpty())
return;
QHelpCollectionHandler collectionHandler(collectionFile);
collectionHandler.setReadOnly(true);
if (!collectionHandler.openCollectionFile())
return;
const QList<QHelpCollectionHandler::ContentsData> result = usesFilterEngine
? collectionHandler.contentsForFilter(currentFilter)
: collectionHandler.contentsForFilter(attributes);
for (const auto &contentsData : result) {
m_mutex.lock();
if (m_abort) {
delete rootItem;
m_abort = false;
m_mutex.unlock();
return;
}
m_mutex.unlock();
const QString namespaceName = contentsData.namespaceName;
const QString folderName = contentsData.folderName;
for (const QByteArray &contents : contentsData.contentsList) {
if (contents.size() < 1)
continue;
int _depth = 0;
bool _root = false;
QStack<QHelpContentItem*> stack;
QDataStream s(contents);
for (;;) {
s >> depth;
s >> link;
s >> title;
if (title.isEmpty())
break;
const QUrl url = constructUrl(namespaceName, folderName, link);
CHECK_DEPTH:
if (depth == 0) {
m_mutex.lock();
item = new QHelpContentItem(title, url, rootItem);
rootItem->d->appendChild(item);
m_mutex.unlock();
stack.push(item);
_depth = 1;
_root = true;
} else {
if (depth > _depth && _root) {
_depth = depth;
stack.push(item);
}
if (depth == _depth) {
item = new QHelpContentItem(title, url, stack.top());
stack.top()->d->appendChild(item);
} else if (depth < _depth) {
stack.pop();
--_depth;
goto CHECK_DEPTH;
}
}
}
}
}
m_mutex.lock();
m_rootItem = rootItem;
m_abort = false;
m_mutex.unlock();
}
/*!
\class QHelpContentModel
\inmodule QtHelp
\brief The QHelpContentModel class provides a model that supplies content to views.
\since 4.4
*/
/*!
\fn void QHelpContentModel::contentsCreationStarted()
This signal is emitted when the creation of the contents has
started. The current contents are invalid from this point on
until the signal contentsCreated() is emitted.
\sa isCreatingContents()
*/
/*!
\fn void QHelpContentModel::contentsCreated()
This signal is emitted when the contents have been created.
*/
QHelpContentModel::QHelpContentModel(QHelpEnginePrivate *helpEngine)
: QAbstractItemModel(helpEngine)
{
d = new QHelpContentModelPrivate();
d->qhelpContentProvider = new QHelpContentProvider(helpEngine);
connect(d->qhelpContentProvider, &QThread::finished,
this, &QHelpContentModel::insertContents);
}
/*!
Destroys the help content model.
*/
QHelpContentModel::~QHelpContentModel()
{
delete d->rootItem;
delete d;
}
/*!
Creates new contents by querying the help system
for contents specified for the \a customFilterName.
*/
void QHelpContentModel::createContents(const QString &customFilterName)
{
const bool running = d->qhelpContentProvider->isRunning();
d->qhelpContentProvider->collectContents(customFilterName);
if (running)
return;
if (d->rootItem) {
beginResetModel();
delete d->rootItem;
d->rootItem = nullptr;
endResetModel();
}
emit contentsCreationStarted();
}
void QHelpContentModel::insertContents()
{
if (d->qhelpContentProvider->isRunning())
return;
QHelpContentItem * const newRootItem = d->qhelpContentProvider->takeContentItem();
if (!newRootItem)
return;
beginResetModel();
delete d->rootItem;
d->rootItem = newRootItem;
endResetModel();
emit contentsCreated();
}
/*!
Returns true if the contents are currently rebuilt, otherwise
false.
*/
bool QHelpContentModel::isCreatingContents() const
{
return d->qhelpContentProvider->isRunning();
}
/*!
Returns the help content item at the model index position
\a index.
*/
QHelpContentItem *QHelpContentModel::contentItemAt(const QModelIndex &index) const
{
if (index.isValid())
return static_cast<QHelpContentItem*>(index.internalPointer());
else
return d->rootItem;
}
/*!
Returns the index of the item in the model specified by
the given \a row, \a column and \a parent index.
*/
QModelIndex QHelpContentModel::index(int row, int column, const QModelIndex &parent) const
{
if (!d->rootItem)
return QModelIndex();
QHelpContentItem *parentItem = contentItemAt(parent);
QHelpContentItem *item = parentItem->child(row);
if (!item)
return QModelIndex();
return createIndex(row, column, item);
}
/*!
Returns the parent of the model item with the given
\a index, or QModelIndex() if it has no parent.
*/
QModelIndex QHelpContentModel::parent(const QModelIndex &index) const
{
QHelpContentItem *item = contentItemAt(index);
if (!item)
return QModelIndex();
QHelpContentItem *parentItem = static_cast<QHelpContentItem*>(item->parent());
if (!parentItem)
return QModelIndex();
QHelpContentItem *grandparentItem = static_cast<QHelpContentItem*>(parentItem->parent());
if (!grandparentItem)
return QModelIndex();
int row = grandparentItem->childPosition(parentItem);
return createIndex(row, index.column(), parentItem);
}
/*!
Returns the number of rows under the given \a parent.
*/
int QHelpContentModel::rowCount(const QModelIndex &parent) const
{
QHelpContentItem *parentItem = contentItemAt(parent);
if (!parentItem)
return 0;
return parentItem->childCount();
}
/*!
Returns the number of columns under the given \a parent. Currently returns always 1.
*/
int QHelpContentModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return 1;
}
/*!
Returns the data stored under the given \a role for
the item referred to by the \a index.
*/
QVariant QHelpContentModel::data(const QModelIndex &index, int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
QHelpContentItem *item = contentItemAt(index);
if (!item)
return QVariant();
return item->title();
}
/*!
\class QHelpContentWidget
\inmodule QtHelp
\brief The QHelpContentWidget class provides a tree view for displaying help content model items.
\since 4.4
*/
/*!
\fn void QHelpContentWidget::linkActivated(const QUrl &link)
This signal is emitted when a content item is activated and
its associated \a link should be shown.
*/
QHelpContentWidget::QHelpContentWidget()
: QTreeView(nullptr)
{
header()->hide();
setUniformRowHeights(true);
connect(this, &QAbstractItemView::activated,
this, &QHelpContentWidget::showLink);
}
/*!
Returns the index of the content item with the \a link.
An invalid index is returned if no such an item exists.
*/
QModelIndex QHelpContentWidget::indexOf(const QUrl &link)
{
QHelpContentModel *contentModel = qobject_cast<QHelpContentModel*>(model());
if (!contentModel || link.scheme() != QLatin1String("qthelp"))
return QModelIndex();
m_syncIndex = QModelIndex();
for (int i = 0; i < contentModel->rowCount(); ++i) {
QHelpContentItem *itm = contentModel->contentItemAt(contentModel->index(i, 0));
if (itm && itm->url().host() == link.host()) {
if (searchContentItem(contentModel, contentModel->index(i, 0), QDir::cleanPath(link.path())))
return m_syncIndex;
}
}
return QModelIndex();
}
bool QHelpContentWidget::searchContentItem(QHelpContentModel *model, const QModelIndex &parent,
const QString &cleanPath)
{
QHelpContentItem *parentItem = model->contentItemAt(parent);
if (!parentItem)
return false;
if (QDir::cleanPath(parentItem->url().path()) == cleanPath) {
m_syncIndex = parent;
return true;
}
for (int i = 0; i < parentItem->childCount(); ++i) {
if (searchContentItem(model, model->index(i, 0, parent), cleanPath))
return true;
}
return false;
}
void QHelpContentWidget::showLink(const QModelIndex &index)
{
QHelpContentModel *contentModel = qobject_cast<QHelpContentModel*>(model());
if (!contentModel)
return;
QHelpContentItem *item = contentModel->contentItemAt(index);
if (!item)
return;
QUrl url = item->url();
if (url.isValid())
emit linkActivated(url);
}
QT_END_NAMESPACE
#include "qhelpcontentwidget.moc"