/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** 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 General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** 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-3.0.html. ** ****************************************************************************/ #include "indexwindow.h" #include "topicchooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Help::Internal; IndexWindow::IndexWindow() : m_searchLineEdit(0) , m_indexWidget(0) , m_isOpenInNewPageActionVisible(true) { QVBoxLayout *layout = new QVBoxLayout(this); m_searchLineEdit = new Utils::FancyLineEdit(); m_searchLineEdit->setPlaceholderText(QString()); m_searchLineEdit->setFiltering(true); setFocusProxy(m_searchLineEdit); connect(m_searchLineEdit, &QLineEdit::textChanged, this, &IndexWindow::filterIndices); m_searchLineEdit->installEventFilter(this); m_searchLineEdit->setAttribute(Qt::WA_MacShowFocusRect, false); QLabel *l = new QLabel(tr("&Look for:")); l->setBuddy(m_searchLineEdit); layout->addWidget(l); layout->setMargin(0); layout->setSpacing(0); Utils::StyledBar *toolbar = new Utils::StyledBar(this); toolbar->setSingleRow(false); QLayout *tbLayout = new QHBoxLayout(); tbLayout->setSpacing(6); tbLayout->setMargin(4); tbLayout->addWidget(l); tbLayout->addWidget(m_searchLineEdit); toolbar->setLayout(tbLayout); layout->addWidget(toolbar); QHelpIndexModel *indexModel = LocalHelpManager::helpEngine().indexModel(); 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); connect(indexModel, &QHelpIndexModel::indexCreationStarted, this, &IndexWindow::disableSearchLineEdit); connect(indexModel, &QHelpIndexModel::indexCreated, this, &IndexWindow::enableSearchLineEdit); connect(m_indexWidget, &Utils::NavigationTreeView::activated, this, [this](const QModelIndex &index) { open(index); }); connect(m_searchLineEdit, &QLineEdit::returnPressed, m_indexWidget, [this]() { open(m_indexWidget->currentIndex()); }); layout->addWidget(m_indexWidget); m_indexWidget->viewport()->installEventFilter(this); } IndexWindow::~IndexWindow() { } void IndexWindow::setOpenInNewPageActionVisible(bool visible) { m_isOpenInNewPageActionVisible = visible; } void IndexWindow::filterIndices(const QString &filter) { QModelIndex bestMatch; if (filter.contains(QLatin1Char('*'))) bestMatch = m_filteredIndexModel->filter(filter, filter); else bestMatch = m_filteredIndexModel->filter(filter, QString()); if (bestMatch.isValid()) { m_indexWidget->setCurrentIndex(bestMatch); m_indexWidget->scrollTo(bestMatch); } } bool IndexWindow::eventFilter(QObject *obj, QEvent *e) { if (obj == m_searchLineEdit && e->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(e); QModelIndex idx = m_indexWidget->currentIndex(); switch (ke->key()) { case Qt::Key_Up: idx = m_indexWidget->model()->index(idx.row()-1, idx.column(), idx.parent()); if (idx.isValid()) m_indexWidget->setCurrentIndex(idx); break; case Qt::Key_Down: idx = m_indexWidget->model()->index(idx.row()+1, idx.column(), idx.parent()); if (idx.isValid()) m_indexWidget->setCurrentIndex(idx); break; default: ; // stop complaining } } else if (obj == m_searchLineEdit && e->type() == QEvent::FocusIn && static_cast(e)->reason() != Qt::MouseFocusReason) { m_searchLineEdit->selectAll(); m_searchLineEdit->setFocus(); } else if (obj == m_indexWidget && e->type() == QEvent::ContextMenu) { QContextMenuEvent *ctxtEvent = static_cast(e); QModelIndex idx = m_indexWidget->indexAt(ctxtEvent->pos()); if (idx.isValid()) { QMenu menu; QAction *curTab = menu.addAction(tr("Open Link")); QAction *newTab = 0; if (m_isOpenInNewPageActionVisible) newTab = menu.addAction(tr("Open Link as New Page")); menu.move(m_indexWidget->mapToGlobal(ctxtEvent->pos())); QAction *action = menu.exec(); if (curTab == action) open(idx); else if (newTab && newTab == action) open(idx, true/*newPage*/); } } else if (m_indexWidget && obj == m_indexWidget->viewport() && e->type() == QEvent::MouseButtonRelease) { QMouseEvent *mouseEvent = static_cast(e); QModelIndex idx = m_indexWidget->indexAt(mouseEvent->pos()); if (idx.isValid()) { Qt::MouseButtons button = mouseEvent->button(); if (((button == Qt::LeftButton) && (mouseEvent->modifiers() & Qt::ControlModifier)) || (button == Qt::MidButton)) { open(idx); } } } return QWidget::eventFilter(obj, e); } void IndexWindow::enableSearchLineEdit() { m_searchLineEdit->setDisabled(false); filterIndices(m_searchLineEdit->text()); } void IndexWindow::disableSearchLineEdit() { m_searchLineEdit->setDisabled(true); } void IndexWindow::open(const QModelIndex &index, bool newPage) { QString keyword = m_filteredIndexModel->data(index, Qt::DisplayRole).toString(); QMap links = LocalHelpManager::helpEngine().indexModel()->linksForKeyword(keyword); emit linksActivated(links, keyword, newPage); } 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, &QAbstractItemModel::dataChanged, this, &IndexFilterModel::sourceDataChanged); disconnect(previousModel, &QAbstractItemModel::rowsInserted, this, &IndexFilterModel::sourceRowsInserted); disconnect(previousModel, &QAbstractItemModel::rowsRemoved, this, &IndexFilterModel::sourceRowsRemoved); disconnect(previousModel, &QAbstractItemModel::modelReset, this, &IndexFilterModel::sourceModelReset); } QAbstractProxyModel::setSourceModel(sm); if (sm) { connect(sm, &QAbstractItemModel::dataChanged, this, &IndexFilterModel::sourceDataChanged); connect(sm, &QAbstractItemModel::rowsInserted, this, &IndexFilterModel::sourceRowsInserted); connect(sm, &QAbstractItemModel::rowsRemoved, this, &IndexFilterModel::sourceRowsRemoved); connect(sm, &QAbstractItemModel::modelReset, this, &IndexFilterModel::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(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); }