Help: Make it possible to create multiple index views

This unfortunately means doing the filtering by hand,
because filtering on the QHelpIndexModel would be shared
between multiple views.

Change-Id: Iae38952a92dbb1b4a9685aea6f057d96f0d0784f
Reviewed-by: Eike Ziller <eike.ziller@digia.com>
This commit is contained in:
Eike Ziller
2014-09-29 17:35:04 +02:00
parent a10f207756
commit 9cc88836f7
5 changed files with 299 additions and 77 deletions

View File

@@ -31,6 +31,7 @@
#include "helpviewer.h" #include "helpviewer.h"
#include "localhelpmanager.h" #include "localhelpmanager.h"
#include "openpagesmanager.h"
#include "topicchooser.h" #include "topicchooser.h"
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
@@ -76,10 +77,18 @@ CentralWidget *CentralWidget::instance()
return gStaticCentralWidget; return gStaticCentralWidget;
} }
void CentralWidget::open(const QUrl &url, bool newPage)
{
if (newPage)
OpenPagesManager::instance().createPage(url);
else
setSource(url);
}
void CentralWidget::showTopicChooser(const QMap<QString, QUrl> &links, void CentralWidget::showTopicChooser(const QMap<QString, QUrl> &links,
const QString &keyword) const QString &keyword, bool newPage)
{ {
TopicChooser tc(this, keyword, links); TopicChooser tc(this, keyword, links);
if (tc.exec() == QDialog::Accepted) if (tc.exec() == QDialog::Accepted)
setSource(tc.link()); open(tc.link(), newPage);
} }

View File

@@ -50,8 +50,11 @@ public:
static CentralWidget *instance(); static CentralWidget *instance();
void open(const QUrl &url, bool newPage = false);
public slots: public slots:
void showTopicChooser(const QMap<QString, QUrl> &links, const QString &key); void showTopicChooser(const QMap<QString, QUrl> &links, const QString &key,
bool newPage = false);
}; };

View File

@@ -299,10 +299,10 @@ void HelpPlugin::setupUi()
indexWindow->setWindowTitle(tr(SB_INDEX)); indexWindow->setWindowTitle(tr(SB_INDEX));
m_indexItem = new SideBarItem(indexWindow, QLatin1String(SB_INDEX)); m_indexItem = new SideBarItem(indexWindow, QLatin1String(SB_INDEX));
connect(indexWindow, SIGNAL(linkActivated(QUrl)), m_centralWidget, connect(indexWindow, &IndexWindow::linkActivated,
SLOT(setSource(QUrl))); m_centralWidget, &CentralWidget::open);
connect(indexWindow, SIGNAL(linksActivated(QMap<QString,QUrl>,QString)), connect(indexWindow, &IndexWindow::linksActivated,
m_centralWidget, SLOT(showTopicChooser(QMap<QString,QUrl>,QString))); m_centralWidget, &CentralWidget::showTopicChooser);
QMap<QString, Command*> shortcutMap; QMap<QString, Command*> shortcutMap;
QAction *action = new QAction(tr("Activate Index in Help mode"), m_splitter); QAction *action = new QAction(tr("Activate Index in Help mode"), m_splitter);

View File

@@ -38,18 +38,19 @@
#include <utils/fancylineedit.h> #include <utils/fancylineedit.h>
#include <utils/hostosinfo.h> #include <utils/hostosinfo.h>
#include <utils/navigationtreeview.h>
#include <utils/styledbar.h> #include <utils/styledbar.h>
#include <QAbstractItemModel>
#include <QLayout> #include <QLayout>
#include <QLabel> #include <QLabel>
#include <QLineEdit> #include <QLineEdit>
#include <QKeyEvent> #include <QKeyEvent>
#include <QMenu> #include <QMenu>
#include <QContextMenuEvent> #include <QContextMenuEvent>
#include <QListWidgetItem>
#include <QHelpEngine> #include <QHelpEngine>
#include <QHelpIndexWidget> #include <QHelpIndexModel>
using namespace Help::Internal; using namespace Help::Internal;
@@ -63,9 +64,10 @@ IndexWindow::IndexWindow()
m_searchLineEdit->setPlaceholderText(QString()); m_searchLineEdit->setPlaceholderText(QString());
m_searchLineEdit->setFiltering(true); m_searchLineEdit->setFiltering(true);
setFocusProxy(m_searchLineEdit); setFocusProxy(m_searchLineEdit);
connect(m_searchLineEdit, SIGNAL(textChanged(QString)), this, connect(m_searchLineEdit, &QLineEdit::textChanged,
SLOT(filterIndices(QString))); this, &IndexWindow::filterIndices);
m_searchLineEdit->installEventFilter(this); m_searchLineEdit->installEventFilter(this);
m_searchLineEdit->setAttribute(Qt::WA_MacShowFocusRect, false);
QLabel *l = new QLabel(tr("&Look for:")); QLabel *l = new QLabel(tr("&Look for:"));
l->setBuddy(m_searchLineEdit); l->setBuddy(m_searchLineEdit);
@@ -83,20 +85,22 @@ IndexWindow::IndexWindow()
toolbar->setLayout(tbLayout); toolbar->setLayout(tbLayout);
layout->addWidget(toolbar); layout->addWidget(toolbar);
QHelpEngine *engine = &LocalHelpManager::helpEngine(); QHelpIndexModel *indexModel = LocalHelpManager::helpEngine().indexModel();
m_indexWidget = engine->indexWidget(); m_filteredIndexModel = new IndexFilterModel(this);
m_filteredIndexModel->setSourceModel(indexModel);
m_indexWidget = new Utils::NavigationTreeView(this);
m_indexWidget->setModel(m_filteredIndexModel);
m_indexWidget->setRootIsDecorated(false);
m_indexWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_indexWidget->installEventFilter(this); m_indexWidget->installEventFilter(this);
connect(engine->indexModel(), SIGNAL(indexCreationStarted()), this, connect(indexModel, &QHelpIndexModel::indexCreationStarted,
SLOT(disableSearchLineEdit())); this, &IndexWindow::disableSearchLineEdit);
connect(engine->indexModel(), SIGNAL(indexCreated()), this, connect(indexModel, &QHelpIndexModel::indexCreated,
SLOT(enableSearchLineEdit())); this, &IndexWindow::enableSearchLineEdit);
connect(m_indexWidget, SIGNAL(linkActivated(QUrl,QString)), this, connect(m_indexWidget, &Utils::NavigationTreeView::activated,
SIGNAL(linkActivated(QUrl))); this, [this](const QModelIndex &index) { open(index); });
connect(m_indexWidget, SIGNAL(linksActivated(QMap<QString,QUrl>,QString)), connect(m_searchLineEdit, &QLineEdit::returnPressed,
this, SIGNAL(linksActivated(QMap<QString,QUrl>,QString))); m_indexWidget, [this]() { open(m_indexWidget->currentIndex()); });
connect(m_searchLineEdit, SIGNAL(returnPressed()), m_indexWidget,
SLOT(activateCurrentItem()));
m_indexWidget->setFrameStyle(QFrame::NoFrame);
layout->addWidget(m_indexWidget); layout->addWidget(m_indexWidget);
m_indexWidget->viewport()->installEventFilter(this); m_indexWidget->viewport()->installEventFilter(this);
@@ -108,10 +112,15 @@ IndexWindow::~IndexWindow()
void IndexWindow::filterIndices(const QString &filter) void IndexWindow::filterIndices(const QString &filter)
{ {
QModelIndex bestMatch;
if (filter.contains(QLatin1Char('*'))) if (filter.contains(QLatin1Char('*')))
m_indexWidget->filterIndices(filter, filter); bestMatch = m_filteredIndexModel->filter(filter, filter);
else else
m_indexWidget->filterIndices(filter, QString()); bestMatch = m_filteredIndexModel->filter(filter, QString());
if (bestMatch.isValid()) {
m_indexWidget->setCurrentIndex(bestMatch);
m_indexWidget->scrollTo(bestMatch);
}
} }
bool IndexWindow::eventFilter(QObject *obj, QEvent *e) bool IndexWindow::eventFilter(QObject *obj, QEvent *e)
@@ -150,9 +159,9 @@ bool IndexWindow::eventFilter(QObject *obj, QEvent *e)
QAction *action = menu.exec(); QAction *action = menu.exec();
if (curTab == action) if (curTab == action)
m_indexWidget->activateCurrentItem(); open(idx);
else if (newTab == action) else if (newTab == action)
open(m_indexWidget, idx); open(idx, true/*newPage*/);
} }
} else if (m_indexWidget && obj == m_indexWidget->viewport() } else if (m_indexWidget && obj == m_indexWidget->viewport()
&& e->type() == QEvent::MouseButtonRelease) { && e->type() == QEvent::MouseButtonRelease) {
@@ -162,16 +171,10 @@ bool IndexWindow::eventFilter(QObject *obj, QEvent *e)
Qt::MouseButtons button = mouseEvent->button(); Qt::MouseButtons button = mouseEvent->button();
if (((button == Qt::LeftButton) && (mouseEvent->modifiers() & Qt::ControlModifier)) if (((button == Qt::LeftButton) && (mouseEvent->modifiers() & Qt::ControlModifier))
|| (button == Qt::MidButton)) { || (button == Qt::MidButton)) {
open(m_indexWidget, idx); open(idx);
} }
} }
} }
else if (Utils::HostOsInfo::isMacHost() && obj == m_indexWidget
&& e->type() == QEvent::KeyPress) {
QKeyEvent *ke = static_cast<QKeyEvent*>(e);
if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter)
m_indexWidget->activateCurrentItem();
}
return QWidget::eventFilter(obj, e); return QWidget::eventFilter(obj, e);
} }
@@ -187,37 +190,203 @@ void IndexWindow::disableSearchLineEdit()
m_searchLineEdit->setDisabled(true); m_searchLineEdit->setDisabled(true);
} }
void IndexWindow::setSearchLineEditText(const QString &text) void IndexWindow::open(const QModelIndex &index, bool newPage)
{ {
m_searchLineEdit->setText(text); QString keyword = m_filteredIndexModel->data(index, Qt::DisplayRole).toString();
} QMap<QString, QUrl> links = LocalHelpManager::helpEngine().indexModel()->linksForKeyword(keyword);
QString IndexWindow::searchLineEditText() const if (links.size() == 1) {
{ emit linkActivated(links.first(), newPage);
return m_searchLineEdit->text(); } else if (links.size() > 1) {
} emit linksActivated(links, keyword, newPage);
void IndexWindow::open(QHelpIndexWidget* indexWidget, const QModelIndex &index)
{
QHelpIndexModel *model = qobject_cast<QHelpIndexModel*>(indexWidget->model());
if (model) {
QString keyword = model->data(index, Qt::DisplayRole).toString();
QMap<QString, QUrl> links = model->linksForKeyword(keyword);
QUrl url;
if (links.count() > 1) {
TopicChooser tc(this, keyword, links);
if (tc.exec() == QDialog::Accepted)
url = tc.link();
} else if (links.count() == 1) {
url = links.constBegin().value();
} else {
return;
}
if (!HelpViewer::canOpenPage(url.path()))
CentralWidget::instance()->setSource(url);
else
OpenPagesManager::instance().createPage(url);
} }
} }
Qt::DropActions IndexFilterModel::supportedDragActions() const
{
return sourceModel()->supportedDragActions();
}
QModelIndex IndexFilterModel::index(int row, int column, const QModelIndex &parent) const
{
Q_UNUSED(parent)
return createIndex(row, column);
}
QModelIndex IndexFilterModel::parent(const QModelIndex &child) const
{
Q_UNUSED(child)
return QModelIndex();
}
int IndexFilterModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return m_toSource.size();
}
int IndexFilterModel::columnCount(const QModelIndex &parent) const
{
return sourceModel()->columnCount(mapToSource(parent));
}
void IndexFilterModel::setSourceModel(QAbstractItemModel *sm)
{
QAbstractItemModel *previousModel = sourceModel();
if (previousModel) {
disconnect(previousModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
this, SLOT(sourceDataChanged(QModelIndex,QModelIndex)));
disconnect(previousModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
disconnect(previousModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
disconnect(previousModel, SIGNAL(modelReset()),
this, SLOT(sourceModelReset()));
}
QAbstractProxyModel::setSourceModel(sm);
if (sm) {
connect(sm, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
this, SLOT(sourceDataChanged(QModelIndex,QModelIndex)));
connect(sm, SIGNAL(rowsInserted(QModelIndex,int,int)),
this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
connect(sm, SIGNAL(rowsRemoved(QModelIndex,int,int)),
this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
connect(sm, SIGNAL(modelReset()),
this, SLOT(sourceModelReset()));
}
filter(m_filter, m_wildcard);
}
QModelIndex IndexFilterModel::sibling(int row, int column, const QModelIndex &idx) const
{
return QAbstractItemModel::sibling(row, column, idx);
}
Qt::ItemFlags IndexFilterModel::flags(const QModelIndex &index) const
{
Q_UNUSED(index)
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
IndexFilterModel::IndexFilterModel(QObject *parent)
: QAbstractProxyModel(parent)
{
}
QModelIndex IndexFilterModel::filter(const QString &filter, const QString &wildcard)
{
beginResetModel();
m_filter = filter;
m_wildcard = wildcard;
m_toSource.clear();
// adapted copy from QHelpIndexModel
if (filter.isEmpty() && wildcard.isEmpty()) {
int count = sourceModel()->rowCount();
m_toSource.reserve(count);
for (int i = 0; i < count; ++i)
m_toSource.append(i);
endResetModel();
return index(0, 0);
}
QHelpIndexModel *indexModel = qobject_cast<QHelpIndexModel *>(sourceModel());
const QStringList indices = indexModel->stringList();
int goodMatch = -1;
int perfectMatch = -1;
if (!wildcard.isEmpty()) {
QRegExp regExp(wildcard, Qt::CaseInsensitive);
regExp.setPatternSyntax(QRegExp::Wildcard);
int i = 0;
foreach (const QString &index, indices) {
if (index.contains(regExp)) {
m_toSource.append(i);
if (perfectMatch == -1 && index.startsWith(filter, Qt::CaseInsensitive)) {
if (goodMatch == -1)
goodMatch = m_toSource.size() - 1;
if (filter.length() == index.length()){
perfectMatch = m_toSource.size() - 1;
}
} else if (perfectMatch > -1 && index == filter) {
perfectMatch = m_toSource.size() - 1;
}
}
++i;
}
} else {
int i = 0;
foreach (const QString &index, indices) {
if (index.contains(filter, Qt::CaseInsensitive)) {
m_toSource.append(i);
if (perfectMatch == -1 && index.startsWith(filter, Qt::CaseInsensitive)) {
if (goodMatch == -1)
goodMatch = m_toSource.size() - 1;
if (filter.length() == index.length()){
perfectMatch = m_toSource.size() - 1;
}
} else if (perfectMatch > -1 && index == filter) {
perfectMatch = m_toSource.size() - 1;
}
}
++i;
}
}
if (perfectMatch == -1)
perfectMatch = qMax(0, goodMatch);
endResetModel();
return index(perfectMatch, 0, QModelIndex());
}
QModelIndex IndexFilterModel::mapToSource(const QModelIndex &proxyIndex) const
{
if (!proxyIndex.isValid() || proxyIndex.parent().isValid() || proxyIndex.row() >= m_toSource.size())
return QModelIndex();
return index(m_toSource.at(proxyIndex.row()), proxyIndex.column());
}
QModelIndex IndexFilterModel::mapFromSource(const QModelIndex &sourceIndex) const
{
if (!sourceIndex.isValid() || sourceIndex.parent().isValid())
return QModelIndex();
int i = m_toSource.indexOf(sourceIndex.row());
if (i < 0)
return QModelIndex();
return index(i, sourceIndex.column());
}
void IndexFilterModel::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
QModelIndex topLeftIndex = mapFromSource(topLeft);
if (!topLeftIndex.isValid())
topLeftIndex = index(0, topLeft.column());
QModelIndex bottomRightIndex = mapFromSource(bottomRight);
if (!bottomRightIndex.isValid())
bottomRightIndex = index(0, bottomRight.column());
emit dataChanged(topLeftIndex, bottomRightIndex);
}
void IndexFilterModel::sourceRowsRemoved(const QModelIndex &parent, int start, int end)
{
Q_UNUSED(parent)
Q_UNUSED(start)
Q_UNUSED(end)
filter(m_filter, m_wildcard);
}
void IndexFilterModel::sourceRowsInserted(const QModelIndex &parent, int start, int end)
{
Q_UNUSED(parent)
Q_UNUSED(start)
Q_UNUSED(end)
filter(m_filter, m_wildcard);
}
void IndexFilterModel::sourceModelReset()
{
filter(m_filter, m_wildcard);
}

View File

@@ -30,15 +30,57 @@
#ifndef INDEXWINDOW_H #ifndef INDEXWINDOW_H
#define INDEXWINDOW_H #define INDEXWINDOW_H
#include <QAbstractProxyModel>
#include <QList>
#include <QUrl> #include <QUrl>
#include <QWidget> #include <QWidget>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QHelpIndexWidget; class QHelpIndexModel;
class QModelIndex; class QModelIndex;
QT_END_NAMESPACE QT_END_NAMESPACE
namespace Utils { class FancyLineEdit; } namespace Utils {
class FancyLineEdit;
class NavigationTreeView;
}
namespace Help {
namespace Internal {
class IndexFilterModel : public QAbstractProxyModel
{
Q_OBJECT
public:
IndexFilterModel(QObject *parent);
QModelIndex filter(const QString &filter, const QString &wildcard);
QModelIndex mapToSource(const QModelIndex &proxyIndex) const;
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const;
Qt::DropActions supportedDragActions() const;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
QModelIndex parent(const QModelIndex &child) const;
int rowCount(const QModelIndex &parent = QModelIndex()) const;
int columnCount(const QModelIndex &parent = QModelIndex()) const;
void setSourceModel(QAbstractItemModel *sm);
// QAbstractProxyModel::sibling is broken in Qt 5
QModelIndex sibling(int row, int column, const QModelIndex &idx) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
private slots:
void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
void sourceRowsRemoved(const QModelIndex &parent, int start, int end);
void sourceRowsInserted(const QModelIndex &parent, int start, int end);
void sourceModelReset();
private:
QString m_filter;
QString m_wildcard;
QList<int> m_toSource;
};
class IndexWindow : public QWidget class IndexWindow : public QWidget
{ {
@@ -48,25 +90,24 @@ public:
IndexWindow(); IndexWindow();
~IndexWindow(); ~IndexWindow();
void setSearchLineEditText(const QString &text);
QString searchLineEditText() const;
signals: signals:
void linkActivated(const QUrl &link); void linkActivated(const QUrl &link, bool newPage);
void linksActivated(const QMap<QString, QUrl> &links, void linksActivated(const QMap<QString, QUrl> &links,
const QString &keyword); const QString &keyword, bool newPage);
private slots: private:
void filterIndices(const QString &filter); void filterIndices(const QString &filter);
void enableSearchLineEdit(); void enableSearchLineEdit();
void disableSearchLineEdit(); void disableSearchLineEdit();
private:
bool eventFilter(QObject *obj, QEvent *e); bool eventFilter(QObject *obj, QEvent *e);
void open(QHelpIndexWidget* indexWidget, const QModelIndex &index); void open(const QModelIndex &index, bool newPage = false);
Utils::FancyLineEdit *m_searchLineEdit; Utils::FancyLineEdit *m_searchLineEdit;
QHelpIndexWidget *m_indexWidget; Utils::NavigationTreeView *m_indexWidget;
IndexFilterModel *m_filteredIndexModel;
}; };
} // Internal
} // Help
#endif // INDEXWINDOW_H #endif // INDEXWINDOW_H