diff options
Diffstat (limited to 'src/assistant')
63 files changed, 2904 insertions, 783 deletions
diff --git a/src/assistant/assistant/aboutdialog.cpp b/src/assistant/assistant/aboutdialog.cpp index 1ac24d2f4..3682f1291 100644 --- a/src/assistant/assistant/aboutdialog.cpp +++ b/src/assistant/assistant/aboutdialog.cpp @@ -40,6 +40,7 @@ #include <QtWidgets/QDesktopWidget> #include <QtWidgets/QMessageBox> #include <QtGui/QDesktopServices> +#include <QtGui/QScreen> QT_BEGIN_NAMESPACE @@ -49,7 +50,7 @@ AboutLabel::AboutLabel(QWidget *parent) TRACE_OBJ setFrameStyle(QFrame::NoFrame); QPalette p; - p.setColor(QPalette::Base, p.color(QPalette::Background)); + p.setColor(QPalette::Base, p.color(QPalette::Window)); setPalette(p); } @@ -141,8 +142,10 @@ QString AboutDialog::documentTitle() const void AboutDialog::updateSize() { TRACE_OBJ - QSize screenSize = QApplication::desktop()->availableGeometry(QCursor::pos()) - .size(); + auto screen = QGuiApplication::screenAt(QCursor::pos()); + if (!screen) + screen = QGuiApplication::primaryScreen(); + const QSize screenSize = screen->availableSize(); int limit = qMin(screenSize.width()/2, 500); #ifdef Q_OS_MAC @@ -156,7 +159,7 @@ void AboutDialog::updateSize() width = limit; QFontMetrics fm(qApp->font("QWorkspaceTitleBar")); - int windowTitleWidth = qMin(fm.width(windowTitle()) + 50, limit); + int windowTitleWidth = qMin(fm.horizontalAdvance(windowTitle()) + 50, limit); if (windowTitleWidth > width) width = windowTitleWidth; diff --git a/src/assistant/assistant/assistant.icns b/src/assistant/assistant/assistant.icns Binary files differindex 1dbfc6a15..cf763571b 100644 --- a/src/assistant/assistant/assistant.icns +++ b/src/assistant/assistant/assistant.icns diff --git a/src/assistant/assistant/assistant.ico b/src/assistant/assistant/assistant.ico Binary files differindex b821faaf2..5fa656451 100644 --- a/src/assistant/assistant/assistant.ico +++ b/src/assistant/assistant/assistant.ico diff --git a/src/assistant/assistant/assistant.pro b/src/assistant/assistant/assistant.pro index 080aba03f..b63be28c7 100644 --- a/src/assistant/assistant/assistant.pro +++ b/src/assistant/assistant/assistant.pro @@ -30,6 +30,7 @@ HEADERS += aboutdialog.h \ helpviewer_p.h \ indexwindow.h \ mainwindow.h \ + optionswidget.h \ preferencesdialog.h \ qtdocinstaller.h \ remotecontrol.h \ @@ -62,6 +63,7 @@ SOURCES += aboutdialog.cpp \ indexwindow.cpp \ main.cpp \ mainwindow.cpp \ + optionswidget.cpp \ preferencesdialog.cpp \ qtdocinstaller.cpp \ remotecontrol.cpp \ diff --git a/src/assistant/assistant/assistant_images.qrc b/src/assistant/assistant/assistant_images.qrc index 948de970f..e55b6b27e 100644 --- a/src/assistant/assistant/assistant_images.qrc +++ b/src/assistant/assistant/assistant_images.qrc @@ -10,7 +10,9 @@ <file>images/mac/editcopy.png</file> <file>images/mac/find.png</file> <file>images/mac/home.png</file> + <file>images/mac/minus.png</file> <file>images/mac/next.png</file> + <file>images/mac/plus.png</file> <file>images/mac/previous.png</file> <file>images/mac/print.png</file> <file>images/mac/synctoc.png</file> @@ -23,7 +25,9 @@ <file>images/win/editcopy.png</file> <file>images/win/find.png</file> <file>images/win/home.png</file> + <file>images/win/minus.png</file> <file>images/win/next.png</file> + <file>images/win/plus.png</file> <file>images/win/previous.png</file> <file>images/win/print.png</file> <file>images/win/synctoc.png</file> diff --git a/src/assistant/assistant/bookmarkfiltermodel.cpp b/src/assistant/assistant/bookmarkfiltermodel.cpp index 05bd30025..a3d5c1191 100644 --- a/src/assistant/assistant/bookmarkfiltermodel.cpp +++ b/src/assistant/assistant/bookmarkfiltermodel.cpp @@ -86,13 +86,13 @@ void BookmarkFilterModel::setSourceModel(QAbstractItemModel *_sourceModel) int BookmarkFilterModel::rowCount(const QModelIndex &index) const { - Q_UNUSED(index) + Q_UNUSED(index); return cache.count(); } int BookmarkFilterModel::columnCount(const QModelIndex &index) const { - Q_UNUSED(index) + Q_UNUSED(index); if (sourceModel) return sourceModel->columnCount(); return 0; @@ -113,14 +113,14 @@ QModelIndex BookmarkFilterModel::mapFromSource(const QModelIndex &sourceIndex) c QModelIndex BookmarkFilterModel::parent(const QModelIndex &child) const { - Q_UNUSED(child) + Q_UNUSED(child); return QModelIndex(); } QModelIndex BookmarkFilterModel::index(int row, int column, const QModelIndex &index) const { - Q_UNUSED(index) + Q_UNUSED(index); if (row < 0 || column < 0 || cache.count() <= row || !sourceModel || sourceModel->columnCount() <= column) { return QModelIndex(); @@ -294,7 +294,7 @@ int BookmarkTreeModel::columnCount(const QModelIndex &parent) const bool BookmarkTreeModel::filterAcceptsRow(int row, const QModelIndex &parent) const { - Q_UNUSED(row) + Q_UNUSED(row); BookmarkModel *model = qobject_cast<BookmarkModel*> (sourceModel()); if (model->rowCount(parent) > 0 && model->data(model->index(row, 0, parent), UserRoleFolder).toBool()) diff --git a/src/assistant/assistant/centralwidget.cpp b/src/assistant/assistant/centralwidget.cpp index 7d2cb06e7..34304f737 100644 --- a/src/assistant/assistant/centralwidget.cpp +++ b/src/assistant/assistant/centralwidget.cpp @@ -193,7 +193,7 @@ CentralWidget::CentralWidget(QWidget *parent) staticCentralWidget = this; QVBoxLayout *vboxLayout = new QVBoxLayout(this); - vboxLayout->setMargin(0); + vboxLayout->setContentsMargins(QMargins()); vboxLayout->setSpacing(0); vboxLayout->addWidget(m_tabBar); m_tabBar->setVisible(HelpEngineWrapper::instance().showTabs()); diff --git a/src/assistant/assistant/contentwindow.cpp b/src/assistant/assistant/contentwindow.cpp index 57bd51261..c932c06b7 100644 --- a/src/assistant/assistant/contentwindow.cpp +++ b/src/assistant/assistant/contentwindow.cpp @@ -51,7 +51,7 @@ ContentWindow::ContentWindow() m_contentWidget->setContextMenuPolicy(Qt::CustomContextMenu); QVBoxLayout *layout = new QVBoxLayout(this); - layout->setMargin(4); + layout->setContentsMargins(4, 4, 4, 4); layout->addWidget(m_contentWidget); connect(m_contentWidget, &QWidget::customContextMenuRequested, diff --git a/src/assistant/assistant/contentwindow.h b/src/assistant/assistant/contentwindow.h index 3fb9c18da..8281ff2d2 100644 --- a/src/assistant/assistant/contentwindow.h +++ b/src/assistant/assistant/contentwindow.h @@ -35,8 +35,6 @@ QT_BEGIN_NAMESPACE -class QHelpEngine; -class QHelpContentItem; class QHelpContentWidget; class ContentWindow : public QWidget diff --git a/src/assistant/assistant/doc/qtassistant.qdocconf b/src/assistant/assistant/doc/qtassistant.qdocconf index e5dcaa8ec..d6c714a6d 100644 --- a/src/assistant/assistant/doc/qtassistant.qdocconf +++ b/src/assistant/assistant/doc/qtassistant.qdocconf @@ -3,6 +3,7 @@ include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf) project = QtAssistant description = Qt Assistant Manual examplesinstallpath = assistant +moduleheader = qhp.projects = QtAssistant diff --git a/src/assistant/assistant/filternamedialog.cpp b/src/assistant/assistant/filternamedialog.cpp index 33b19cff8..4c17d3332 100644 --- a/src/assistant/assistant/filternamedialog.cpp +++ b/src/assistant/assistant/filternamedialog.cpp @@ -25,7 +25,6 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ -#include "tracer.h" #include <QtWidgets/QPushButton> @@ -36,7 +35,6 @@ QT_BEGIN_NAMESPACE FilterNameDialog::FilterNameDialog(QWidget *parent) : QDialog(parent) { - TRACE_OBJ m_ui.setupUi(this); connect(m_ui.buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, this, &QDialog::accept); @@ -47,15 +45,19 @@ FilterNameDialog::FilterNameDialog(QWidget *parent) m_ui.buttonBox->button(QDialogButtonBox::Ok)->setDisabled(true); } +void FilterNameDialog::setFilterName(const QString &filter) +{ + m_ui.lineEdit->setText(filter); + m_ui.lineEdit->selectAll(); +} + QString FilterNameDialog::filterName() const { - TRACE_OBJ return m_ui.lineEdit->text(); } void FilterNameDialog::updateOkButton() { - TRACE_OBJ m_ui.buttonBox->button(QDialogButtonBox::Ok) ->setDisabled(m_ui.lineEdit->text().isEmpty()); } diff --git a/src/assistant/assistant/filternamedialog.h b/src/assistant/assistant/filternamedialog.h index 8e46bfd0b..522b611af 100644 --- a/src/assistant/assistant/filternamedialog.h +++ b/src/assistant/assistant/filternamedialog.h @@ -40,6 +40,8 @@ class FilterNameDialog : public QDialog public: FilterNameDialog(QWidget *parent = nullptr); + + void setFilterName(const QString &filter); QString filterName() const; private slots: diff --git a/src/assistant/assistant/filternamedialog.ui b/src/assistant/assistant/filternamedialog.ui index 755a93479..1da584a80 100644 --- a/src/assistant/assistant/filternamedialog.ui +++ b/src/assistant/assistant/filternamedialog.ui @@ -1,67 +1,55 @@ -<ui version="4.0" > +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> <class>FilterNameDialogClass</class> - <widget class="QDialog" name="FilterNameDialogClass" > - <property name="geometry" > + <widget class="QDialog" name="FilterNameDialogClass"> + <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>312</width> - <height>95</height> + <height>77</height> </rect> </property> - <property name="windowTitle" > - <string>Add Filter Name</string> + <property name="windowTitle"> + <string>Add Filter</string> </property> - <layout class="QGridLayout" > - <property name="margin" > - <number>9</number> - </property> - <property name="spacing" > - <number>6</number> - </property> - <item row="0" column="0" > - <widget class="QLabel" name="label" > - <property name="text" > + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> <string>Filter Name:</string> </property> </widget> </item> - <item row="0" column="1" colspan="2" > - <widget class="QLineEdit" name="lineEdit" /> + <item row="0" column="1"> + <widget class="QLineEdit" name="lineEdit"/> </item> - <item row="1" column="0" colspan="3" > - <widget class="Line" name="line" > - <property name="orientation" > - <enum>Qt::Horizontal</enum> - </property> - </widget> - </item> - <item row="2" column="0" colspan="2" > - <spacer> - <property name="orientation" > - <enum>Qt::Horizontal</enum> + <item row="1" column="0"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> </property> - <property name="sizeHint" > + <property name="sizeHint" stdset="0"> <size> - <width>40</width> - <height>20</height> + <width>20</width> + <height>1</height> </size> </property> </spacer> </item> - <item row="2" column="2" > - <widget class="QDialogButtonBox" name="buttonBox" > - <property name="orientation" > + <item row="2" column="0" colspan="2"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> <enum>Qt::Horizontal</enum> </property> - <property name="standardButtons" > - <set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> </property> </widget> </item> </layout> </widget> - <layoutdefault spacing="6" margin="11" /> + <layoutdefault spacing="6" margin="11"/> <resources/> <connections/> </ui> diff --git a/src/assistant/assistant/findwidget.cpp b/src/assistant/assistant/findwidget.cpp index b9607b703..2bbbbf6b0 100644 --- a/src/assistant/assistant/findwidget.cpp +++ b/src/assistant/assistant/findwidget.cpp @@ -49,7 +49,7 @@ FindWidget::FindWidget(QWidget *parent) QString resourcePath = QLatin1String(":/qt-project.org/assistant/images/"); #ifndef Q_OS_MAC - hboxLayout->setMargin(0); + hboxLayout->setContentsMargins(QMargins()); hboxLayout->setSpacing(6); resourcePath.append(QLatin1String("win")); #else diff --git a/src/assistant/assistant/globalactions.cpp b/src/assistant/assistant/globalactions.cpp index 945f025a3..6accd6dcd 100644 --- a/src/assistant/assistant/globalactions.cpp +++ b/src/assistant/assistant/globalactions.cpp @@ -224,9 +224,9 @@ void GlobalActions::setupNavigationMenus(QAction *back, QAction *next, this, &GlobalActions::slotOpenActionUrl); next->setMenu(m_nextMenu); #else - Q_UNUSED(back) - Q_UNUSED(next) - Q_UNUSED(parent) + Q_UNUSED(back); + Q_UNUSED(next); + Q_UNUSED(parent); #endif } diff --git a/src/assistant/assistant/helpenginewrapper.cpp b/src/assistant/assistant/helpenginewrapper.cpp index 3aef5bb99..17e4bde8c 100644 --- a/src/assistant/assistant/helpenginewrapper.cpp +++ b/src/assistant/assistant/helpenginewrapper.cpp @@ -39,13 +39,13 @@ #include <QtCore/QTimer> #include <QtHelp/QHelpContentModel> #include <QtHelp/QHelpEngine> +#include <QtHelp/QHelpFilterEngine> #include <QtHelp/QHelpIndexModel> #include <QtHelp/QHelpSearchEngine> QT_BEGIN_NAMESPACE namespace { - const QString Unfiltered; const QString AppFontKey(QLatin1String("appFont")); const QString AppWritingSystemKey(QLatin1String("appWritingSystem")); const QString BookmarksKey(QLatin1String("Bookmarks")); @@ -104,14 +104,6 @@ private: QMap<QString, RecentSignal> m_recentQchUpdates; }; -const QString HelpEngineWrapper::TrUnfiltered() -{ - static QString s; - if (s.isEmpty()) - s = HelpEngineWrapper::tr("Unfiltered"); - return s; -} - HelpEngineWrapper *HelpEngineWrapper::helpEngineWrapper = nullptr; HelpEngineWrapper &HelpEngineWrapper::instance(const QString &collectionFile) @@ -154,8 +146,6 @@ HelpEngineWrapper::HelpEngineWrapper(const QString &collectionFile) this, &HelpEngineWrapper::documentationRemoved); connect(d, &HelpEngineWrapperPrivate::documentationUpdated, this, &HelpEngineWrapper::documentationUpdated); - connect(d->m_helpEngine, &QHelpEngineCore::currentFilterChanged, - this, &HelpEngineWrapper::handleCurrentFilterChanged); connect(d->m_helpEngine, &QHelpEngineCore::setupFinished, this, &HelpEngineWrapper::setupFinished); } @@ -259,43 +249,6 @@ bool HelpEngineWrapper::setupData() return d->m_helpEngine->setupData(); } -bool HelpEngineWrapper::addCustomFilter(const QString &filterName, - const QStringList &attributes) -{ - TRACE_OBJ - return d->m_helpEngine->addCustomFilter(filterName, attributes); -} - -bool HelpEngineWrapper::removeCustomFilter(const QString &filterName) -{ - TRACE_OBJ - return d->m_helpEngine->removeCustomFilter(filterName); -} - -void HelpEngineWrapper::setCurrentFilter(const QString ¤tFilter) -{ - TRACE_OBJ - const QString &filter - = currentFilter == TrUnfiltered() ? Unfiltered : currentFilter; - d->m_helpEngine->setCurrentFilter(filter); -} - -const QString HelpEngineWrapper::currentFilter() const -{ - TRACE_OBJ - const QString &filter = d->m_helpEngine->currentFilter(); - return filter == Unfiltered ? TrUnfiltered() : filter; -} - -const QStringList HelpEngineWrapper::customFilters() const -{ - TRACE_OBJ - QStringList filters = d->m_helpEngine->customFilters(); - filters.removeOne(Unfiltered); - filters.prepend(TrUnfiltered()); - return filters; -} - QUrl HelpEngineWrapper::findFile(const QUrl &url) const { TRACE_OBJ @@ -314,22 +267,15 @@ QMap<QString, QUrl> HelpEngineWrapper::linksForIdentifier(const QString &id) con return d->m_helpEngine->linksForIdentifier(id); } -const QStringList HelpEngineWrapper::filterAttributes() const -{ - TRACE_OBJ - return d->m_helpEngine->filterAttributes(); -} - -const QStringList HelpEngineWrapper::filterAttributes(const QString &filterName) const +QString HelpEngineWrapper::error() const { TRACE_OBJ - return d->m_helpEngine->filterAttributes(filterName); + return d->m_helpEngine->error(); } -QString HelpEngineWrapper::error() const +QHelpFilterEngine *HelpEngineWrapper::filterEngine() const { - TRACE_OBJ - return d->m_helpEngine->error(); + return d->m_helpEngine->filterEngine(); } const QStringList HelpEngineWrapper::qtDocInfo(const QString &component) const @@ -694,14 +640,6 @@ void HelpEngineWrapper::setBrowserWritingSystem(QFontDatabase::WritingSystem sys d->m_helpEngine->setCustomValue(BrowserWritingSystemKey, system); } -void HelpEngineWrapper::handleCurrentFilterChanged(const QString &filter) -{ - TRACE_OBJ - const QString &filterToReport - = filter == Unfiltered ? TrUnfiltered() : filter; - emit currentFilterChanged(filterToReport); -} - bool HelpEngineWrapper::showTabs() const { TRACE_OBJ @@ -753,9 +691,8 @@ HelpEngineWrapperPrivate::HelpEngineWrapperPrivate(const QString &collectionFile m_qchWatcher(new QFileSystemWatcher(this)) { TRACE_OBJ - if (!m_helpEngine->customFilters().contains(Unfiltered)) - m_helpEngine->addCustomFilter(Unfiltered, QStringList()); initFileSystemWatchers(); + m_helpEngine->setUsesFilterEngine(true); } void HelpEngineWrapperPrivate::initFileSystemWatchers() diff --git a/src/assistant/assistant/helpenginewrapper.h b/src/assistant/assistant/helpenginewrapper.h index 08474f697..6026a0473 100644 --- a/src/assistant/assistant/helpenginewrapper.h +++ b/src/assistant/assistant/helpenginewrapper.h @@ -45,6 +45,7 @@ class QHelpContentWidget; class QHelpIndexModel; class QHelpIndexWidget; class QHelpSearchEngine; +class QHelpFilterEngine; enum { ShowHomePage = 0, @@ -76,19 +77,13 @@ public: const QString collectionFile() const; bool registerDocumentation(const QString &docFile); bool unregisterDocumentation(const QString &namespaceName); - bool addCustomFilter(const QString &filterName, - const QStringList &attributes); - bool removeCustomFilter(const QString &filterName); - void setCurrentFilter(const QString &filterName); - const QString currentFilter() const; - const QStringList customFilters() const; QUrl findFile(const QUrl &url) const; QByteArray fileData(const QUrl &url) const; QMap<QString, QUrl> linksForIdentifier(const QString &id) const; - const QStringList filterAttributes() const; - const QStringList filterAttributes(const QString &filterName) const; QString error() const; + QHelpFilterEngine *filterEngine() const; + /* * To be called after assistant has finished looking for new documentation. * This will mainly cause the search index to be updated, if necessary. @@ -175,8 +170,6 @@ public: bool showTabs() const; void setShowTabs(bool show); - static const QString TrUnfiltered(); - bool fullTextSearchFallbackEnabled() const; const QByteArray topicChooserGeometry() const; @@ -189,12 +182,8 @@ signals: void documentationUpdated(const QString &namespaceName); // Forwarded from QHelpEngineCore. - void currentFilterChanged(const QString ¤tFilter); void setupFinished(); -private slots: - void handleCurrentFilterChanged(const QString &filter); - private: HelpEngineWrapper(const QString &collectionFile); ~HelpEngineWrapper(); diff --git a/src/assistant/assistant/helpviewer_qwv.cpp b/src/assistant/assistant/helpviewer_qwv.cpp index 1551d9c81..f8acb7b46 100644 --- a/src/assistant/assistant/helpviewer_qwv.cpp +++ b/src/assistant/assistant/helpviewer_qwv.cpp @@ -230,7 +230,7 @@ QString HelpViewer::title() const void HelpViewer::setTitle(const QString &title) { TRACE_OBJ - Q_UNUSED(title) + Q_UNUSED(title); } QUrl HelpViewer::source() const diff --git a/src/assistant/assistant/images/assistant-128.png b/src/assistant/assistant/images/assistant-128.png Binary files differindex a5d8fea1a..62c66ed8f 100644 --- a/src/assistant/assistant/images/assistant-128.png +++ b/src/assistant/assistant/images/assistant-128.png diff --git a/src/assistant/assistant/images/assistant.png b/src/assistant/assistant/images/assistant.png Binary files differindex e6d7312a8..6f124afb6 100644 --- a/src/assistant/assistant/images/assistant.png +++ b/src/assistant/assistant/images/assistant.png diff --git a/src/assistant/assistant/images/mac/minus.png b/src/assistant/assistant/images/mac/minus.png Binary files differnew file mode 100644 index 000000000..8d2eaed52 --- /dev/null +++ b/src/assistant/assistant/images/mac/minus.png diff --git a/src/assistant/assistant/images/mac/plus.png b/src/assistant/assistant/images/mac/plus.png Binary files differnew file mode 100644 index 000000000..1ee45423e --- /dev/null +++ b/src/assistant/assistant/images/mac/plus.png diff --git a/src/assistant/assistant/images/win/minus.png b/src/assistant/assistant/images/win/minus.png Binary files differnew file mode 100644 index 000000000..c0dc274bb --- /dev/null +++ b/src/assistant/assistant/images/win/minus.png diff --git a/src/assistant/assistant/images/win/plus.png b/src/assistant/assistant/images/win/plus.png Binary files differnew file mode 100644 index 000000000..ecf058941 --- /dev/null +++ b/src/assistant/assistant/images/win/plus.png diff --git a/src/assistant/assistant/indexwindow.cpp b/src/assistant/assistant/indexwindow.cpp index 5dd13c3bb..d8c0f8212 100644 --- a/src/assistant/assistant/indexwindow.cpp +++ b/src/assistant/assistant/indexwindow.cpp @@ -62,7 +62,7 @@ IndexWindow::IndexWindow(QWidget *parent) connect(m_searchLineEdit, &QLineEdit::textChanged, this, &IndexWindow::filterIndices); m_searchLineEdit->installEventFilter(this); - layout->setMargin(4); + layout->setContentsMargins(4, 4, 4, 4); layout->addWidget(m_searchLineEdit); HelpEngineWrapper &helpEngine = HelpEngineWrapper::instance(); diff --git a/src/assistant/assistant/mainwindow.cpp b/src/assistant/assistant/mainwindow.cpp index b5288771d..2b9c87bbf 100644 --- a/src/assistant/assistant/mainwindow.cpp +++ b/src/assistant/assistant/mainwindow.cpp @@ -64,6 +64,7 @@ #include <QtWidgets/QDockWidget> #include <QtGui/QFontDatabase> #include <QtGui/QImageReader> +#include <QtGui/QScreen> #include <QtWidgets/QFileDialog> #include <QtWidgets/QLabel> #include <QtWidgets/QLayout> @@ -80,6 +81,7 @@ #include <QtHelp/QHelpEngineCore> #include <QtHelp/QHelpIndexModel> #include <QtHelp/QHelpSearchEngine> +#include <QtHelp/QHelpFilterEngine> #include <cstdlib> @@ -215,7 +217,7 @@ MainWindow::MainWindow(CmdLineParser *cmdLine, QWidget *parent) tabifyDockWidget(indexDock, bookmarkDock); tabifyDockWidget(bookmarkDock, searchDock); contentDock->raise(); - const QRect screen = QApplication::desktop()->screenGeometry(); + const QRect screen = QGuiApplication::primaryScreen()->geometry(); resize(4*screen.width()/5, 4*screen.height()/5); adjustSize(); // make sure we won't start outside of the screen @@ -268,8 +270,8 @@ MainWindow::MainWindow(CmdLineParser *cmdLine, QWidget *parent) if (!m_cmdLine->currentFilter().isEmpty()) { const QString &curFilter = m_cmdLine->currentFilter(); - if (helpEngineWrapper.customFilters().contains(curFilter)) - helpEngineWrapper.setCurrentFilter(curFilter); + if (helpEngineWrapper.filterEngine()->filters().contains(curFilter)) + helpEngineWrapper.filterEngine()->setActiveFilter(curFilter); } if (usesDefaultCollection()) @@ -723,7 +725,7 @@ void MainWindow::setupFilterToolbar() m_filterCombo = new QComboBox(this); m_filterCombo->setMinimumWidth(QFontMetrics(QFont()). - width(QLatin1String("MakeTheComboBoxWidthEnough"))); + horizontalAdvance(QLatin1String("MakeTheComboBoxWidthEnough"))); QToolBar *filterToolBar = addToolBar(tr("Filter Toolbar")); filterToolBar->setObjectName(QLatin1String("FilterToolBar")); @@ -737,9 +739,9 @@ void MainWindow::setupFilterToolbar() connect(&helpEngine, &HelpEngineWrapper::setupFinished, this, &MainWindow::setupFilterCombo, Qt::QueuedConnection); - connect(m_filterCombo, QOverload<const QString &>::of(&QComboBox::activated), + connect(m_filterCombo, QOverload<int>::of(&QComboBox::activated), this, &MainWindow::filterDocumentation); - connect(&helpEngine, &HelpEngineWrapper::currentFilterChanged, + connect(helpEngine.filterEngine(), &QHelpFilterEngine::filterActivated, this, &MainWindow::currentFilterChanged); setupFilterCombo(); @@ -1048,21 +1050,27 @@ void MainWindow::setupFilterCombo() { TRACE_OBJ HelpEngineWrapper &helpEngine = HelpEngineWrapper::instance(); - QString curFilter = m_filterCombo->currentText(); - if (curFilter.isEmpty()) - curFilter = helpEngine.currentFilter(); + const QString currentFilter = helpEngine.filterEngine()->activeFilter(); m_filterCombo->clear(); - m_filterCombo->addItems(helpEngine.customFilters()); - int idx = m_filterCombo->findText(curFilter); + m_filterCombo->addItem(tr("Unfiltered")); + const QStringList allFilters = helpEngine.filterEngine()->filters(); + if (!allFilters.isEmpty()) + m_filterCombo->insertSeparator(1); + for (const QString &filter : allFilters) + m_filterCombo->addItem(filter, filter); + + int idx = m_filterCombo->findData(currentFilter); if (idx < 0) idx = 0; m_filterCombo->setCurrentIndex(idx); } -void MainWindow::filterDocumentation(const QString &customFilter) +void MainWindow::filterDocumentation(int filterIndex) { TRACE_OBJ - HelpEngineWrapper::instance().setCurrentFilter(customFilter); + + const QString filter = m_filterCombo->itemData(filterIndex).toString(); + HelpEngineWrapper::instance().filterEngine()->setActiveFilter(filter); } void MainWindow::expandTOC(int depth) @@ -1091,7 +1099,7 @@ void MainWindow::indexingStarted() progressBar->setSizePolicy(sizePolicy); hlayout->setSpacing(6); - hlayout->setMargin(0); + hlayout->setContentsMargins(QMargins()); hlayout->addWidget(progressBar); statusBar()->addPermanentWidget(m_progressWidget); @@ -1143,8 +1151,9 @@ QString MainWindow::defaultHelpCollectionFileName() void MainWindow::currentFilterChanged(const QString &filter) { TRACE_OBJ - const int index = m_filterCombo->findText(filter); - Q_ASSERT(index != -1); + int index = m_filterCombo->findData(filter); + if (index < 0) + index = 0; m_filterCombo->setCurrentIndex(index); } diff --git a/src/assistant/assistant/mainwindow.h b/src/assistant/assistant/mainwindow.h index 657a15825..c5bf5837e 100644 --- a/src/assistant/assistant/mainwindow.h +++ b/src/assistant/assistant/mainwindow.h @@ -37,7 +37,6 @@ QT_BEGIN_NAMESPACE class QAction; class QComboBox; -class QFileSystemWatcher; class QLineEdit; class QMenu; @@ -45,10 +44,7 @@ class CentralWidget; class CmdLineParser; class ContentWindow; class IndexWindow; -class OpenPagesWindow; class QtDocInstaller; -class QHelpEngineCore; -class QHelpEngine; class SearchWidget; class MainWindow : public QMainWindow @@ -94,7 +90,7 @@ private slots: void showNewAddress(const QUrl &url); void showTopicChooser(const QMap<QString, QUrl> &links, const QString &keyword); void updateApplicationFont(); - void filterDocumentation(const QString &customFilter); + void filterDocumentation(int filterIndex); void setupFilterCombo(); void lookForNewQtDocumentation(); void indexingStarted(); diff --git a/src/assistant/assistant/openpagesmanager.cpp b/src/assistant/assistant/openpagesmanager.cpp index 3151cb534..f3acb4656 100644 --- a/src/assistant/assistant/openpagesmanager.cpp +++ b/src/assistant/assistant/openpagesmanager.cpp @@ -237,7 +237,7 @@ void OpenPagesManager::closeOrReloadPages(const QString &nameSpace, bool tryRelo HelpViewer *page = m_model->pageAt(i); if (page->source().host() != nameSpace) continue; - if (tryReload && HelpEngineWrapper::instance().findFile(page->source()).isValid()) + if (tryReload && HelpEngineWrapper::instance().findFile(page->source()).isValid()) page->reload(); else if (m_model->rowCount() == 1) page->setSource(QUrl(QLatin1String("about:blank"))); diff --git a/src/assistant/assistant/openpagesswitcher.cpp b/src/assistant/assistant/openpagesswitcher.cpp index df9350140..ee932def9 100644 --- a/src/assistant/assistant/openpagesswitcher.cpp +++ b/src/assistant/assistant/openpagesswitcher.cpp @@ -63,7 +63,7 @@ OpenPagesSwitcher::OpenPagesSwitcher(OpenPagesModel *model) m_openPagesWidget->installEventFilter(this); QVBoxLayout *layout = new QVBoxLayout(this); - layout->setMargin(0); + layout->setContentsMargins(QMargins()); layout->addWidget(m_openPagesWidget); connect(m_openPagesWidget, &OpenPagesWidget::closePage, @@ -113,7 +113,7 @@ void OpenPagesSwitcher::setVisible(bool visible) void OpenPagesSwitcher::focusInEvent(QFocusEvent *event) { TRACE_OBJ - Q_UNUSED(event) + Q_UNUSED(event); m_openPagesWidget->setFocus(); } diff --git a/src/assistant/assistant/optionswidget.cpp b/src/assistant/assistant/optionswidget.cpp new file mode 100644 index 000000000..bc089c5bf --- /dev/null +++ b/src/assistant/assistant/optionswidget.cpp @@ -0,0 +1,234 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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:GPL-EXCEPT$ +** 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. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "optionswidget.h" + +#include <QtWidgets/QComboBox> +#include <QtWidgets/QItemDelegate> +#include <QtWidgets/QListWidget> +#include <QtWidgets/QVBoxLayout> + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +class ListWidgetDelegate : public QItemDelegate +{ +// Q_OBJECT not needed +public: + ListWidgetDelegate(QWidget *w) : QItemDelegate(w), m_widget(w) {} + + static bool isSeparator(const QModelIndex &index) { + return index.data(Qt::AccessibleDescriptionRole).toString() == QLatin1String("separator"); + } + static void setSeparator(QListWidgetItem *item) { + item->setData(Qt::AccessibleDescriptionRole, QString::fromLatin1("separator")); + item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled)); + } + +protected: + void paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const override { + if (isSeparator(index)) { + QRect rect = option.rect; + if (const QAbstractItemView *view = qobject_cast<const QAbstractItemView*>(option.widget)) + rect.setWidth(view->viewport()->width()); + QStyleOption opt; + opt.rect = rect; + m_widget->style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &opt, painter, m_widget); + } else { + QItemDelegate::paint(painter, option, index); + } + } + + QSize sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const override { + if (isSeparator(index)) { + int pm = m_widget->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, m_widget); + return QSize(pm, pm); + } + return QItemDelegate::sizeHint(option, index); + } +private: + QWidget *m_widget; +}; + +static QStringList subtract(const QStringList &minuend, const QStringList &subtrahend) +{ + QStringList result = minuend; + for (const QString &str : subtrahend) + result.removeOne(str); + return result; +} + +///////////////// + +OptionsWidget::OptionsWidget(QWidget *parent) + : QWidget(parent) + , m_noOptionText(tr("No Option")) + , m_invalidOptionText(tr("Invalid Option")) +{ + m_listWidget = new QListWidget(this); + m_listWidget->setItemDelegate(new ListWidgetDelegate(m_listWidget)); + QVBoxLayout *layout = new QVBoxLayout(this); + layout->addWidget(m_listWidget); + layout->setContentsMargins(QMargins()); + + connect(m_listWidget, &QListWidget::itemChanged, this, &OptionsWidget::itemChanged); +} + +void OptionsWidget::clear() +{ + setOptions(QStringList(), QStringList()); +} + +void OptionsWidget::setOptions(const QStringList &validOptions, + const QStringList &selectedOptions) +{ + m_listWidget->clear(); + m_optionToItem.clear(); + m_itemToOption.clear(); + + m_validOptions = validOptions; + m_validOptions.removeDuplicates(); + std::sort(m_validOptions.begin(), m_validOptions.end()); + + m_selectedOptions = selectedOptions; + m_selectedOptions.removeDuplicates(); + std::sort(m_selectedOptions.begin(), m_selectedOptions.end()); + + m_invalidOptions = subtract(m_selectedOptions, m_validOptions); + const QStringList validSelectedOptions = subtract(m_selectedOptions, m_invalidOptions); + const QStringList validUnselectedOptions = subtract(m_validOptions, m_selectedOptions); + + for (const QString &option : validSelectedOptions) + appendItem(option, true, true); + + for (const QString &option : m_invalidOptions) + appendItem(option, false, true); + + if ((validSelectedOptions.count() + m_invalidOptions.count()) + && validUnselectedOptions.count()) { + appendSeparator(); + } + + for (const QString &option : validUnselectedOptions) { + appendItem(option, true, false); + if (option.isEmpty() && validUnselectedOptions.count() > 1) // special No Option item + appendSeparator(); + } +} + +QStringList OptionsWidget::validOptions() const +{ + return m_validOptions; +} + +QStringList OptionsWidget::selectedOptions() const +{ + return m_selectedOptions; +} + +void OptionsWidget::setNoOptionText(const QString &text) +{ + if (m_noOptionText == text) + return; + + m_noOptionText = text; + + // update GUI + const auto itEnd = m_optionToItem.constEnd(); + for (auto it = m_optionToItem.constBegin(); it != itEnd; ++it) { + const QString optionName = it.key(); + if (optionName.isEmpty()) + it.value()->setText(optionText(optionName, m_validOptions.contains(optionName))); + } +} + +void OptionsWidget::setInvalidOptionText(const QString &text) +{ + if (m_invalidOptionText == text) + return; + + m_invalidOptionText = text; + + // update GUI + for (const QString &option : m_invalidOptions) + m_optionToItem.value(option)->setText(optionText(option, false)); +} + +QString OptionsWidget::optionText(const QString &optionName, bool valid) const +{ + QString text = optionName; + if (optionName.isEmpty()) + text = QLatin1Char('[') + m_noOptionText + QLatin1Char(']'); + if (!valid) + text += QLatin1String("\t[") + m_invalidOptionText + QLatin1Char(']'); + return text; +} + +QListWidgetItem *OptionsWidget::appendItem(const QString &optionName, bool valid, bool selected) +{ + QListWidgetItem *optionItem = new QListWidgetItem(optionText(optionName, valid), m_listWidget); + optionItem->setCheckState(selected ? Qt::Checked : Qt::Unchecked); + m_listWidget->insertItem(m_listWidget->count(), optionItem); + m_optionToItem[optionName] = optionItem; + m_itemToOption[optionItem] = optionName; + return optionItem; +} + +void OptionsWidget::appendSeparator() +{ + QListWidgetItem *separatorItem = new QListWidgetItem(m_listWidget); + ListWidgetDelegate::setSeparator(separatorItem); + m_listWidget->insertItem(m_listWidget->count(), separatorItem); +} + +void OptionsWidget::itemChanged(QListWidgetItem *item) +{ + const auto it = m_itemToOption.constFind(item); + if (it == m_itemToOption.constEnd()) + return; + + const QString option = *it; + + if (item->checkState() == Qt::Checked && !m_selectedOptions.contains(option)) { + m_selectedOptions.append(option); + std::sort(m_selectedOptions.begin(), m_selectedOptions.end()); + } else if (item->checkState() == Qt::Unchecked && m_selectedOptions.contains(option)) { + m_selectedOptions.removeOne(option); + } else { + return; + } + + emit optionSelectionChanged(m_selectedOptions); +} + + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/optionswidget.h b/src/assistant/assistant/optionswidget.h new file mode 100644 index 000000000..52c876bad --- /dev/null +++ b/src/assistant/assistant/optionswidget.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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:GPL-EXCEPT$ +** 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. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef OPTIONSWIDGET_H +#define OPTIONSWIDGET_H + +#include <QtWidgets/QWidget> +#include <QtCore/QMap> + +QT_BEGIN_NAMESPACE + +class QListWidget; +class QListWidgetItem; + +class OptionsWidget : public QWidget +{ + Q_OBJECT +public: + OptionsWidget(QWidget *parent = nullptr); + + void clear(); + void setOptions(const QStringList &validOptions, + const QStringList &selectedOptions); + QStringList validOptions() const; + QStringList selectedOptions() const; + + void setNoOptionText(const QString &text); + void setInvalidOptionText(const QString &text); + +signals: + void optionSelectionChanged(const QStringList &options); + +private: + QString optionText(const QString &optionName, bool valid) const; + QListWidgetItem *appendItem(const QString &optionName, bool valid, bool selected); + void appendSeparator(); + void itemChanged(QListWidgetItem *item); + + QListWidget *m_listWidget = nullptr; + QString m_noOptionText; + QString m_invalidOptionText; + QStringList m_validOptions; + QStringList m_invalidOptions; + QStringList m_selectedOptions; + QMap<QString, QListWidgetItem *> m_optionToItem; + QMap<QListWidgetItem *, QString> m_itemToOption; +}; + +QT_END_NAMESPACE + +#endif // OPTIONSWIDGET_H diff --git a/src/assistant/assistant/preferencesdialog.cpp b/src/assistant/assistant/preferencesdialog.cpp index 796c674ea..7955e08df 100644 --- a/src/assistant/assistant/preferencesdialog.cpp +++ b/src/assistant/assistant/preferencesdialog.cpp @@ -32,105 +32,36 @@ #include "fontpanel.h" #include "helpenginewrapper.h" #include "openpagesmanager.h" -#include "tracer.h" -#include <QtCore/QAbstractListModel> -#include <QtCore/QtAlgorithms> -#include <QtCore/QFileSystemWatcher> -#include <QtCore/QSortFilterProxyModel> -#include <QtCore/QVector> - -#include <QtWidgets/QDesktopWidget> -#include <QtWidgets/QFileDialog> +#include <QtCore/QVersionNumber> #include <QtGui/QFontDatabase> -#include <QtWidgets/QHeaderView> -#include <QtWidgets/QMenu> #include <QtWidgets/QMessageBox> +#include <QtHelp/QCompressedHelpInfo> #include <QtHelp/QHelpEngineCore> +#include <QtHelp/QHelpFilterData> +#include <QtHelp/QHelpFilterEngine> -#include <algorithm> - -QT_BEGIN_NAMESPACE - -struct RegisteredDocEntry -{ - QString nameSpace; - QString fileName; -}; - -typedef QVector<RegisteredDocEntry> RegisteredDocEntries; - -class RegisteredDocsModel : public QAbstractListModel { -public: - - explicit RegisteredDocsModel(const RegisteredDocEntries &e = RegisteredDocEntries(), QObject *parent = nullptr) - : QAbstractListModel(parent), m_docEntries(e) {} - - int rowCount(const QModelIndex & = QModelIndex()) const override { return m_docEntries.size(); } - QVariant data(const QModelIndex &index, int role) const override; - - bool contains(const QString &nameSpace) const - { - return m_docEntries.cend() != - std::find_if(m_docEntries.cbegin(), m_docEntries.cend(), - [nameSpace] (const RegisteredDocEntry &e) { return e.nameSpace == nameSpace; }); - } - - void append(const RegisteredDocEntry &e); - - const RegisteredDocEntries &docEntries() const { return m_docEntries; } - void setDocEntries(const RegisteredDocEntries &); +#include <QtWidgets/QFileDialog> -private: - RegisteredDocEntries m_docEntries; -}; +#include <QtDebug> -QVariant RegisteredDocsModel::data(const QModelIndex &index, int role) const -{ - QVariant result; - const int row = index.row(); - if (index.isValid() && row < m_docEntries.size()) { - switch (role) { - case Qt::DisplayRole: - result = QVariant(m_docEntries.at(row).nameSpace); - break; - case Qt::ToolTipRole: - result = QVariant(QDir::toNativeSeparators(m_docEntries.at(row).fileName)); - break; - default: - break; - } - } - return result; -} - -void RegisteredDocsModel::append(const RegisteredDocEntry &e) -{ - beginInsertRows(QModelIndex(), m_docEntries.size(), m_docEntries.size()); - m_docEntries.append(e); - endInsertRows(); -} +QT_BEGIN_NAMESPACE -void RegisteredDocsModel::setDocEntries(const RegisteredDocEntries &e) +static QStringList versionsToStringList(const QList<QVersionNumber> &versions) { - beginResetModel(); - m_docEntries = e; - endResetModel(); + QStringList versionList; + for (const QVersionNumber &version : versions) + versionList.append(version.isNull() ? QString() : version.toString()); + return versionList; } -static RegisteredDocEntries registeredDocEntries(const HelpEngineWrapper &wrapper) +static QList<QVersionNumber> stringListToVersions(const QStringList &versionList) { - RegisteredDocEntries result; - const QStringList &nameSpaces = wrapper.registeredDocumentations(); - result.reserve(nameSpaces.size()); - for (const QString &nameSpace : nameSpaces) { - RegisteredDocEntry entry; - entry.nameSpace = nameSpace; - entry.fileName = wrapper.documentationFileName(nameSpace); - result.append(entry); - } - return result; + QList<QVersionNumber> versions; + for (const QString &versionString : versionList) + versions.append(QVersionNumber::fromString(versionString)); + return versions; } PreferencesDialog::PreferencesDialog(QWidget *parent) @@ -141,52 +72,67 @@ PreferencesDialog::PreferencesDialog(QWidget *parent) , m_hideFiltersTab(!helpEngine.filterFunctionalityEnabled()) , m_hideDocsTab(!helpEngine.documentationManagerEnabled()) { - TRACE_OBJ m_ui.setupUi(this); - m_registeredDocsModel = - new RegisteredDocsModel(m_hideDocsTab ? RegisteredDocEntries() : registeredDocEntries(helpEngine)); - m_registereredDocsFilterModel = new QSortFilterProxyModel(m_ui.registeredDocsListView); - m_registereredDocsFilterModel->setSourceModel(m_registeredDocsModel); - m_ui.registeredDocsListView->setModel(m_registereredDocsFilterModel); - connect(m_ui.registeredDocsFilterLineEdit, &QLineEdit::textChanged, - m_registereredDocsFilterModel, &QSortFilterProxyModel::setFilterFixedString); + QString resourcePath = QLatin1String(":/qt-project.org/assistant/images/"); +#ifdef Q_OS_MACOS + resourcePath.append(QLatin1String("mac")); +#else + resourcePath.append(QLatin1String("win")); +#endif + + m_ui.filterAddButton->setIcon(QIcon(resourcePath + QLatin1String("/plus.png"))); + m_ui.filterRemoveButton->setIcon(QIcon(resourcePath + QLatin1String("/minus.png"))); + + // TODO: filter docs via lineedit connect(m_ui.buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, - this, &PreferencesDialog::applyChanges); + this, &PreferencesDialog::okClicked); + connect(m_ui.buttonBox->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, + this, &PreferencesDialog::applyClicked); connect(m_ui.buttonBox->button(QDialogButtonBox::Cancel), &QAbstractButton::clicked, this, &QDialog::reject); - if (!m_hideFiltersTab) { - m_ui.attributeWidget->header()->hide(); - m_ui.attributeWidget->setRootIsDecorated(false); - - connect(m_ui.attributeWidget, &QTreeWidget::itemChanged, - this, &PreferencesDialog::updateFilterMap); - connect(m_ui.filterWidget, &QListWidget::currentItemChanged, - this, &PreferencesDialog::updateAttributes); + m_originalSetup = readOriginalSetup(); + m_currentSetup = m_originalSetup; - connect(m_ui.filterAddButton, &QAbstractButton::clicked, - this, &PreferencesDialog::addFilter); - connect(m_ui.filterRemoveButton, &QAbstractButton::clicked, - this, &PreferencesDialog::removeFilter); - - updateFilterPage(); + if (m_hideDocsTab) { + m_ui.tabWidget->removeTab(m_ui.tabWidget->indexOf(m_ui.docsTab)); } else { - m_ui.tabWidget->removeTab(m_ui.tabWidget->indexOf(m_ui.filtersTab)); - } - - if (!m_hideDocsTab) { connect(m_ui.docAddButton, &QAbstractButton::clicked, - this, &PreferencesDialog::addDocumentationLocal); + this, &PreferencesDialog::addDocumentation); connect(m_ui.docRemoveButton, &QAbstractButton::clicked, this, &PreferencesDialog::removeDocumentation); - m_docsBackup.reserve(m_registeredDocsModel->rowCount()); - for (const RegisteredDocEntry &e : m_registeredDocsModel->docEntries()) - m_docsBackup.append(e.nameSpace); + updateDocumentationPage(); + } + + if (m_hideFiltersTab) { + m_ui.tabWidget->removeTab(m_ui.tabWidget->indexOf(m_ui.filtersTab)); } else { - m_ui.tabWidget->removeTab(m_ui.tabWidget->indexOf(m_ui.docsTab)); + connect(m_ui.componentWidget, &OptionsWidget::optionSelectionChanged, + this, &PreferencesDialog::componentsChanged); + connect(m_ui.versionWidget, &OptionsWidget::optionSelectionChanged, + this, &PreferencesDialog::versionsChanged); + connect(m_ui.filterWidget, &QListWidget::currentItemChanged, + this, &PreferencesDialog::filterSelected); + connect(m_ui.filterWidget, &QListWidget::itemDoubleClicked, + this, &PreferencesDialog::renameFilterClicked); + + // TODO: repeat these actions on context menu + connect(m_ui.filterAddButton, &QAbstractButton::clicked, + this, &PreferencesDialog::addFilterClicked); + connect(m_ui.filterRenameButton, &QAbstractButton::clicked, + this, &PreferencesDialog::renameFilterClicked); + connect(m_ui.filterRemoveButton, &QAbstractButton::clicked, + this, &PreferencesDialog::removeFilterClicked); + + m_ui.componentWidget->setNoOptionText(tr("No Component")); + m_ui.componentWidget->setInvalidOptionText(tr("Invalid Component")); + m_ui.versionWidget->setNoOptionText(tr("No Version")); + m_ui.versionWidget->setInvalidOptionText(tr("Invalid Version")); + + updateFilterPage(); } updateFontSettingsPage(); @@ -198,7 +144,6 @@ PreferencesDialog::PreferencesDialog(QWidget *parent) PreferencesDialog::~PreferencesDialog() { - TRACE_OBJ if (m_appFontChanged) { helpEngine.setAppFont(m_appFontPanel->selectedFont()); helpEngine.setUseAppFont(m_appFontPanel->isChecked()); @@ -222,260 +167,406 @@ PreferencesDialog::~PreferencesDialog() helpEngine.setStartOption(option); } +FilterSetup PreferencesDialog::readOriginalSetup() const +{ + FilterSetup filterSetup; + + filterSetup.m_namespaceToComponent = helpEngine.filterEngine()->namespaceToComponent(); + filterSetup.m_namespaceToVersion = helpEngine.filterEngine()->namespaceToVersion(); + for (auto it = filterSetup.m_namespaceToComponent.constBegin(); + it != filterSetup.m_namespaceToComponent.constEnd(); ++it) { + const QString namespaceName = it.key(); + const QString namespaceFileName = helpEngine.documentationFileName(namespaceName); + filterSetup.m_namespaceToFileName.insert(namespaceName, namespaceFileName); + filterSetup.m_fileNameToNamespace.insert(namespaceFileName, namespaceName); + filterSetup.m_componentToNamespace[it.value()].append(namespaceName); + } + for (auto it = filterSetup.m_namespaceToVersion.constBegin(); + it != filterSetup.m_namespaceToVersion.constEnd(); ++it) { + filterSetup.m_versionToNamespace[it.value()].append(it.key()); + } + + const QStringList allFilters = helpEngine.filterEngine()->filters(); + for (const QString &filter : allFilters) + filterSetup.m_filterToData.insert(filter, helpEngine.filterEngine()->filterData(filter)); + + filterSetup.m_currentFilter = helpEngine.filterEngine()->activeFilter(); + + return filterSetup; +} + void PreferencesDialog::showDialog() { - TRACE_OBJ if (exec() != Accepted) m_appFontChanged = m_browserFontChanged = false; } void PreferencesDialog::updateFilterPage() { - TRACE_OBJ - m_ui.filterWidget->clear(); - m_ui.attributeWidget->clear(); + if (m_hideFiltersTab) + return; - m_filterMapBackup.clear(); - const QStringList &filters = helpEngine.customFilters(); - for (const QString &filter : filters) { - if (filter == HelpEngineWrapper::TrUnfiltered()) - continue; - QStringList atts = helpEngine.filterAttributes(filter); - m_filterMapBackup.insert(filter, atts); - if (!m_filterMap.contains(filter)) - m_filterMap.insert(filter, atts); + QString currentFilter = m_itemToFilter.value(m_ui.filterWidget->currentItem()); + if (currentFilter.isEmpty()) + currentFilter = m_currentSetup.m_currentFilter; + + m_currentSetup = m_originalSetup; + + m_ui.filterWidget->clear(); + m_ui.componentWidget->clear(); + m_ui.versionWidget->clear(); + m_itemToFilter.clear(); + m_filterToItem.clear(); + + for (const QString &filterName : m_currentSetup.m_filterToData.keys()) { + QListWidgetItem *item = new QListWidgetItem(filterName); + m_ui.filterWidget->addItem(item); + m_itemToFilter.insert(item, filterName); + m_filterToItem.insert(filterName, item); + if (filterName == currentFilter) + m_ui.filterWidget->setCurrentItem(item); } - m_ui.filterWidget->addItems(m_filterMap.keys()); + if (!m_ui.filterWidget->currentItem() && !m_filterToItem.isEmpty()) + m_ui.filterWidget->setCurrentItem(m_filterToItem.first()); - for (const QString &a : helpEngine.filterAttributes()) - new QTreeWidgetItem(m_ui.attributeWidget, QStringList() << a); + updateCurrentFilter(); +} - if (!m_filterMap.isEmpty()) - m_ui.filterWidget->setCurrentRow(0); +void PreferencesDialog::updateCurrentFilter() +{ + if (m_hideFiltersTab) + return; + + const QString ¤tFilter = m_itemToFilter.value(m_ui.filterWidget->currentItem()); + + const bool filterSelected = !currentFilter.isEmpty(); + m_ui.componentWidget->setEnabled(filterSelected); + m_ui.versionWidget->setEnabled(filterSelected); + m_ui.filterRenameButton->setEnabled(filterSelected); + m_ui.filterRemoveButton->setEnabled(filterSelected); + + m_ui.componentWidget->setOptions(m_currentSetup.m_componentToNamespace.keys(), + m_currentSetup.m_filterToData.value(currentFilter).components()); + m_ui.versionWidget->setOptions(versionsToStringList(m_currentSetup.m_versionToNamespace.keys()), + versionsToStringList(m_currentSetup.m_filterToData.value(currentFilter).versions())); } -void PreferencesDialog::updateAttributes(QListWidgetItem *item) +void PreferencesDialog::updateDocumentationPage() { - TRACE_OBJ - const QStringList &checkedList = item ? m_filterMap.value(item->text()) : QStringList(); + if (m_hideDocsTab) + return; - for (int i = 0; i < m_ui.attributeWidget->topLevelItemCount(); ++i) { - QTreeWidgetItem *itm = m_ui.attributeWidget->topLevelItem(i); - if (checkedList.contains(itm->text(0))) - itm->setCheckState(0, Qt::Checked); - else - itm->setCheckState(0, Qt::Unchecked); + m_ui.registeredDocsListWidget->clear(); + m_namespaceToItem.clear(); + m_itemToNamespace.clear(); + + for (const QString &namespaceName : m_currentSetup.m_namespaceToFileName.keys()) { + QListWidgetItem *item = new QListWidgetItem(namespaceName); + m_namespaceToItem.insert(namespaceName, item); + m_itemToNamespace.insert(item, namespaceName); + m_ui.registeredDocsListWidget->addItem(item); } } -void PreferencesDialog::updateFilterMap() +void PreferencesDialog::filterSelected(QListWidgetItem *item) +{ + Q_UNUSED(item) + + updateCurrentFilter(); +} + +void PreferencesDialog::componentsChanged(const QStringList &components) { - TRACE_OBJ - if (!m_ui.filterWidget->currentItem()) + const QString ¤tFilter = m_itemToFilter.value(m_ui.filterWidget->currentItem()); + if (currentFilter.isEmpty()) return; - QString filter = m_ui.filterWidget->currentItem()->text(); - if (!m_filterMap.contains(filter)) + + m_currentSetup.m_filterToData[currentFilter].setComponents(components); +} + +void PreferencesDialog::versionsChanged(const QStringList &versions) +{ + const QString ¤tFilter = m_itemToFilter.value(m_ui.filterWidget->currentItem()); + if (currentFilter.isEmpty()) return; - QStringList newAtts; - QTreeWidgetItem *itm = nullptr; - for (int i = 0; i < m_ui.attributeWidget->topLevelItemCount(); ++i) { - itm = m_ui.attributeWidget->topLevelItem(i); - if (itm->checkState(0) == Qt::Checked) - newAtts.append(itm->text(0)); + m_currentSetup.m_filterToData[currentFilter].setVersions(stringListToVersions(versions)); +} + +QString PreferencesDialog::suggestedNewFilterName(const QString &initialFilterName) const +{ + QString newFilterName = initialFilterName; + + int counter = 1; + while (m_filterToItem.contains(newFilterName)) { + newFilterName = initialFilterName + QLatin1Char(' ') + + QString::number(++counter); } - m_filterMap[filter] = newAtts; + + return newFilterName; } -void PreferencesDialog::addFilter() +QString PreferencesDialog::getUniqueFilterName(const QString &windowTitle, + const QString &initialFilterName) { - TRACE_OBJ - FilterNameDialog dia(this); - if (dia.exec() == QDialog::Rejected) - return; + QString newFilterName = initialFilterName; + while (1) { + FilterNameDialog dialog(this); + dialog.setWindowTitle(windowTitle); + dialog.setFilterName(newFilterName); + if (dialog.exec() == QDialog::Rejected) + return QString(); + + newFilterName = dialog.filterName(); + if (!m_filterToItem.contains(newFilterName)) + break; - QString filterName = dia.filterName(); - if (!m_filterMap.contains(filterName)) { - m_filterMap.insert(filterName, QStringList()); - m_ui.filterWidget->addItem(filterName); + if (QMessageBox::warning(this, tr("Filter Exists"), + tr("The filter \"%1\" already exists.") + .arg(newFilterName), + QMessageBox::Retry | QMessageBox::Cancel) + == QMessageBox::Cancel) { + return QString(); + } } - QList<QListWidgetItem*> lst = m_ui.filterWidget - ->findItems(filterName, Qt::MatchCaseSensitive); - m_ui.filterWidget->setCurrentItem(lst.first()); + return newFilterName; +} + +void PreferencesDialog::addFilterClicked() +{ + const QString newFilterName = getUniqueFilterName(tr("Add Filter"), + suggestedNewFilterName(tr("New Filter"))); + if (newFilterName.isEmpty()) + return; + + addFilter(newFilterName); } -void PreferencesDialog::removeFilter() +void PreferencesDialog::renameFilterClicked() { - TRACE_OBJ - QListWidgetItem *item = - m_ui.filterWidget ->takeItem(m_ui.filterWidget->currentRow()); - if (!item) + const QString ¤tFilter = m_itemToFilter.value(m_ui.filterWidget->currentItem()); + if (currentFilter.isEmpty()) + return; + + const QString newFilterName = getUniqueFilterName(tr("Rename Filter"), currentFilter); + if (newFilterName.isEmpty()) return; - m_filterMap.remove(item->text()); - m_removedFilters.append(item->text()); + const QHelpFilterData oldFilterData = m_currentSetup.m_filterToData.value(currentFilter); + removeFilter(currentFilter); + addFilter(newFilterName, oldFilterData); + + if (m_currentSetup.m_currentFilter == currentFilter) + m_currentSetup.m_currentFilter = newFilterName; +} + +void PreferencesDialog::removeFilterClicked() +{ + const QString ¤tFilter = m_itemToFilter.value(m_ui.filterWidget->currentItem()); + if (currentFilter.isEmpty()) + return; + + if (QMessageBox::question(this, tr("Remove Filter"), + tr("Are you sure you want to remove the \"%1\" filter?") + .arg(currentFilter), + QMessageBox::Yes | QMessageBox::No) + != QMessageBox::Yes) { + return; + } + + removeFilter(currentFilter); + + if (m_currentSetup.m_currentFilter == currentFilter) + m_currentSetup.m_currentFilter.clear(); +} + +void PreferencesDialog::addFilter(const QString &filterName, + const QHelpFilterData &filterData) +{ + QListWidgetItem *item = new QListWidgetItem(filterName); + m_currentSetup.m_filterToData.insert(filterName, filterData); + m_filterToItem.insert(filterName, item); + m_itemToFilter.insert(item, filterName); + m_ui.filterWidget->insertItem(m_filterToItem.keys().indexOf(filterName), item); + + m_ui.filterWidget->setCurrentItem(item); + updateCurrentFilter(); +} + +void PreferencesDialog::removeFilter(const QString &filterName) +{ + QListWidgetItem *item = m_filterToItem.value(filterName); + m_itemToFilter.remove(item); + m_filterToItem.remove(filterName); delete item; - if (m_ui.filterWidget->count()) - m_ui.filterWidget->setCurrentRow(0); + + m_currentSetup.m_filterToData.remove(filterName); } -void PreferencesDialog::addDocumentationLocal() +void PreferencesDialog::addDocumentation() { - TRACE_OBJ const QStringList &fileNames = QFileDialog::getOpenFileNames(this, tr("Add Documentation"), QString(), tr("Qt Compressed Help Files (*.qch)")); if (fileNames.isEmpty()) return; - QStringList invalidFiles; - QStringList alreadyRegistered; + bool added = false; + for (const QString &fileName : fileNames) { - const QString nameSpace = QHelpEngineCore::namespaceName(fileName); - if (nameSpace.isEmpty()) { - invalidFiles.append(fileName); + const QCompressedHelpInfo info = QCompressedHelpInfo::fromCompressedHelpFile(fileName); + const QString namespaceName = info.namespaceName(); + + if (m_currentSetup.m_namespaceToFileName.contains(namespaceName)) continue; - } - if (m_registeredDocsModel->contains(nameSpace)) { - alreadyRegistered.append(nameSpace); - continue; - } + if (m_currentSetup.m_fileNameToNamespace.contains(fileName)) + continue; - if (helpEngine.registerDocumentation(fileName)) { - RegisteredDocEntry entry; - entry.nameSpace = nameSpace; - entry.fileName = fileName; - m_registeredDocsModel->append(entry); - m_regDocs.append(nameSpace); - m_unregDocs.removeAll(nameSpace); - } - } + const QString component = info.component(); + const QVersionNumber version = info.version(); - if (!invalidFiles.isEmpty() || !alreadyRegistered.isEmpty()) { - QString message; - if (!alreadyRegistered.isEmpty()) { - for (const QString &ns : qAsConst(alreadyRegistered)) { - message += tr("The namespace %1 is already registered!") - .arg(QString("<b>%1</b>").arg(ns)) + QLatin1String("<br>"); - } - if (!invalidFiles.isEmpty()) - message.append(QLatin1String("<br>")); - } + m_currentSetup.m_namespaceToFileName.insert(namespaceName, fileName); + m_currentSetup.m_fileNameToNamespace.insert(fileName, namespaceName); - if (!invalidFiles.isEmpty()) { - message += tr("The specified file is not a valid Qt Help File!"); - message.append(QLatin1String("<ul>")); - for (const QString &file : qAsConst(invalidFiles)) - message += QLatin1String("<li>") + file + QLatin1String("</li>"); - message.append(QLatin1String("</ul>")); - } - QMessageBox::warning(this, tr("Add Documentation"), message); + m_currentSetup.m_namespaceToComponent.insert(namespaceName, component); + m_currentSetup.m_componentToNamespace[component].append(namespaceName); + + m_currentSetup.m_namespaceToVersion.insert(namespaceName, version); + m_currentSetup.m_versionToNamespace[version].append(namespaceName); + + QListWidgetItem *item = new QListWidgetItem(namespaceName); + m_namespaceToItem.insert(namespaceName, item); + m_itemToNamespace.insert(item, namespaceName); + m_ui.registeredDocsListWidget->insertItem(m_namespaceToItem.keys().indexOf(namespaceName), item); + + added = true; } - updateFilterPage(); + if (added) + updateCurrentFilter(); } -QList<int> PreferencesDialog::currentRegisteredDocsSelection() const +void PreferencesDialog::removeDocumentation() { - QList<int> result; - for (const QModelIndex &index : m_ui.registeredDocsListView->selectionModel()->selectedRows()) - result.append(m_registereredDocsFilterModel->mapToSource(index).row()); - std::sort(result.begin(), result.end()); - return result; + const QList<QListWidgetItem *> selectedItems = m_ui.registeredDocsListWidget->selectedItems(); + if (selectedItems.isEmpty()) + return; + + for (QListWidgetItem *item : selectedItems) { + const QString namespaceName = m_itemToNamespace.value(item); + m_itemToNamespace.remove(item); + m_namespaceToItem.remove(namespaceName); + delete item; + + const QString fileName = m_currentSetup.m_namespaceToFileName.value(namespaceName); + const QString component = m_currentSetup.m_namespaceToComponent.value(namespaceName); + const QVersionNumber version = m_currentSetup.m_namespaceToVersion.value(namespaceName); + m_currentSetup.m_namespaceToComponent.remove(namespaceName); + m_currentSetup.m_namespaceToVersion.remove(namespaceName); + m_currentSetup.m_namespaceToFileName.remove(namespaceName); + m_currentSetup.m_fileNameToNamespace.remove(fileName); + m_currentSetup.m_componentToNamespace[component].removeOne(namespaceName); + if (m_currentSetup.m_componentToNamespace[component].isEmpty()) + m_currentSetup.m_componentToNamespace.remove(component); + m_currentSetup.m_versionToNamespace[version].removeOne(namespaceName); + if (m_currentSetup.m_versionToNamespace[version].isEmpty()) + m_currentSetup.m_versionToNamespace.remove(version); + } + + updateCurrentFilter(); } -void PreferencesDialog::removeDocumentation() +void PreferencesDialog::okClicked() { - TRACE_OBJ + applyChanges(); + accept(); +} - const QList<int> currentSelection = currentRegisteredDocsSelection(); - if (currentSelection.isEmpty()) - return; +void PreferencesDialog::applyClicked() +{ + applyChanges(); + m_originalSetup = readOriginalSetup(); + m_currentSetup = m_originalSetup; + updateDocumentationPage(); + updateFilterPage(); +} - RegisteredDocEntries entries = m_registeredDocsModel->docEntries(); - - bool foundBefore = false; - for (int i = currentSelection.size() - 1; i >= 0; --i) { - const int row = currentSelection.at(i); - const QString &ns = entries.at(row).nameSpace; - if (!foundBefore && OpenPagesManager::instance()->pagesOpenForNamespace(ns)) { - if (0 == QMessageBox::information(this, tr("Remove Documentation"), - tr("Some documents currently opened in Assistant reference the " - "documentation you are attempting to remove. Removing the " - "documentation will close those documents."), tr("Cancel"), - tr("OK"))) return; - foundBefore = true; - } +template <class T> +static QMap<QString, T> subtract(const QMap<QString, T> &minuend, + const QMap<QString, T> &subtrahend) +{ + QMap<QString, T> result = minuend; - m_unregDocs.append(ns); - entries.removeAt(row); + for (auto itSubtrahend = subtrahend.cbegin(); itSubtrahend != subtrahend.cend(); ++itSubtrahend) { + auto itResult = result.find(itSubtrahend.key()); + if (itResult != result.end() && itSubtrahend.value() == itResult.value()) + result.erase(itResult); } - m_registeredDocsModel->setDocEntries(entries); - - if (m_registereredDocsFilterModel->rowCount()) { - const QModelIndex &first = m_registereredDocsFilterModel->index(0, 0); - m_ui.registeredDocsListView->selectionModel()->setCurrentIndex(first, - QItemSelectionModel::ClearAndSelect); - } + return result; } void PreferencesDialog::applyChanges() { - TRACE_OBJ - bool filtersWereChanged = false; - if (!m_hideFiltersTab) { - if (m_filterMap.count() != m_filterMapBackup.count()) { - filtersWereChanged = true; - } else { - for (auto it = m_filterMapBackup.cbegin(), end = m_filterMapBackup.cend(); it != end && !filtersWereChanged; ++it) { - if (!m_filterMap.contains(it.key())) { - filtersWereChanged = true; - } else { - const QStringList &a = it.value(); - const QStringList &b = m_filterMap.value(it.key()); - if (a.count() != b.count()) { - filtersWereChanged = true; - } else { - for (const QString &aStr : a) { - if (!b.contains(aStr)) { - filtersWereChanged = true; - break; - } - } - } - } - } - } + bool changed = false; + + const QMap<QString, QString> docsToRemove = subtract( + m_originalSetup.m_namespaceToFileName, + m_currentSetup.m_namespaceToFileName); + const QMap<QString, QString> docsToAdd = subtract( + m_currentSetup.m_namespaceToFileName, + m_originalSetup.m_namespaceToFileName); + + for (const QString &namespaceName : docsToRemove.keys()) { + if (!helpEngine.unregisterDocumentation(namespaceName)) + qWarning() << "Cannot unregister documentation:" << namespaceName; + changed = true; + } + + for (const QString &fileName : docsToAdd.values()) { + if (!helpEngine.registerDocumentation(fileName)) + qWarning() << "Cannot register documentation file:" << fileName; + changed = true; } - if (filtersWereChanged) { - for (const QString &filter : qAsConst(m_removedFilters)) - helpEngine.removeCustomFilter(filter); - for (auto it = m_filterMap.cbegin(), end = m_filterMap.cend(); it != end; ++it) - helpEngine.addCustomFilter(it.key(), it.value()); + const QMap<QString, QHelpFilterData> filtersToRemove = subtract( + m_originalSetup.m_filterToData, + m_currentSetup.m_filterToData); + const QMap<QString, QHelpFilterData> filtersToAdd = subtract( + m_currentSetup.m_filterToData, + m_originalSetup.m_filterToData); + + const QString ¤tFilter = helpEngine.filterEngine()->activeFilter(); + + for (const QString &filter : filtersToRemove.keys()) { + helpEngine.filterEngine()->removeFilter(filter); + if (currentFilter == filter && !filtersToAdd.contains(filter)) + helpEngine.filterEngine()->setActiveFilter(QString()); + changed = true; } - for (const QString &doc : qAsConst(m_unregDocs)) { - OpenPagesManager::instance()->closePages(doc); - helpEngine.unregisterDocumentation(doc); + for (auto it = filtersToAdd.cbegin(); it != filtersToAdd.cend(); ++it) { + helpEngine.filterEngine()->setFilterData(it.key(), it.value()); + changed = true; } - if (filtersWereChanged || !m_regDocs.isEmpty() || !m_unregDocs.isEmpty()) + if (changed) { + helpEngine.filterEngine()->setActiveFilter(m_currentSetup.m_currentFilter); + + // In order to update the filtercombobox and indexwidget + // according to the new filter configuration. helpEngine.setupData(); + } helpEngine.setShowTabs(m_ui.showTabs->isChecked()); if (m_showTabs != m_ui.showTabs->isChecked()) emit updateUserInterface(); - - accept(); } void PreferencesDialog::updateFontSettingsPage() { - TRACE_OBJ m_browserFontPanel = new FontPanel(this); m_browserFontPanel->setCheckable(true); m_ui.stackedWidget_2->insertWidget(0, m_browserFontPanel); @@ -527,35 +618,30 @@ void PreferencesDialog::updateFontSettingsPage() void PreferencesDialog::appFontSettingToggled(bool on) { - TRACE_OBJ - Q_UNUSED(on) + Q_UNUSED(on); m_appFontChanged = true; } void PreferencesDialog::appFontSettingChanged(int index) { - TRACE_OBJ - Q_UNUSED(index) + Q_UNUSED(index); m_appFontChanged = true; } void PreferencesDialog::browserFontSettingToggled(bool on) { - TRACE_OBJ - Q_UNUSED(on) + Q_UNUSED(on); m_browserFontChanged = true; } void PreferencesDialog::browserFontSettingChanged(int index) { - TRACE_OBJ - Q_UNUSED(index) + Q_UNUSED(index); m_browserFontChanged = true; } void PreferencesDialog::updateOptionsPage() { - TRACE_OBJ m_ui.homePageLineEdit->setText(helpEngine.homePage()); int option = helpEngine.startOption(); @@ -574,13 +660,11 @@ void PreferencesDialog::updateOptionsPage() void PreferencesDialog::setBlankPage() { - TRACE_OBJ m_ui.homePageLineEdit->setText(QLatin1String("about:blank")); } void PreferencesDialog::setCurrentPage() { - TRACE_OBJ QString homepage = CentralWidget::instance()->currentSource().toString(); if (homepage.isEmpty()) homepage = QLatin1String("help"); @@ -590,7 +674,6 @@ void PreferencesDialog::setCurrentPage() void PreferencesDialog::setDefaultPage() { - TRACE_OBJ m_ui.homePageLineEdit->setText(helpEngine.defaultHomePage()); } diff --git a/src/assistant/assistant/preferencesdialog.h b/src/assistant/assistant/preferencesdialog.h index 4842dcbfb..89c30c568 100644 --- a/src/assistant/assistant/preferencesdialog.h +++ b/src/assistant/assistant/preferencesdialog.h @@ -30,15 +30,29 @@ #define PREFERENCESDIALOG_H #include <QtWidgets/QDialog> +#include <QtHelp/QHelpFilterData> #include "ui_preferencesdialog.h" QT_BEGIN_NAMESPACE class FontPanel; class HelpEngineWrapper; -class RegisteredDocsModel; class QFileSystemWatcher; -class QSortFilterProxyModel; +class QVersionNumber; + +struct FilterSetup { + QMap<QString, QString> m_namespaceToComponent; + QMap<QString, QStringList> m_componentToNamespace; + + QMap<QString, QVersionNumber> m_namespaceToVersion; + QMap<QVersionNumber, QStringList> m_versionToNamespace; + + QMap<QString, QString> m_namespaceToFileName; + QMap<QString, QString> m_fileNameToNamespace; + + QMap<QString, QHelpFilterData> m_filterToData; + QString m_currentFilter; +}; class PreferencesDialog : public QDialog { @@ -51,12 +65,19 @@ public: void showDialog(); private slots: - void updateAttributes(QListWidgetItem *item); - void updateFilterMap(); - void addFilter(); - void removeFilter(); - void addDocumentationLocal(); + void filterSelected(QListWidgetItem *item); + void componentsChanged(const QStringList &components); + void versionsChanged(const QStringList &versions); + void addFilterClicked(); + void renameFilterClicked(); + void removeFilterClicked(); + void addFilter(const QString &filterName, + const QHelpFilterData &filterData = QHelpFilterData()); + void removeFilter(const QString &filterName); + void addDocumentation(); void removeDocumentation(); + void okClicked(); + void applyClicked(); void applyChanges(); void appFontSettingToggled(bool on); void appFontSettingChanged(int index); @@ -73,20 +94,27 @@ signals: void updateUserInterface(); private: + QString suggestedNewFilterName(const QString &initialFilterName) const; + QString getUniqueFilterName(const QString &windowTitle, + const QString &initialFilterName = QString()); void updateFilterPage(); + void updateCurrentFilter(); + void updateDocumentationPage(); void updateFontSettingsPage(); void updateOptionsPage(); - QList<int> currentRegisteredDocsSelection() const; + FilterSetup readOriginalSetup() const; Ui::PreferencesDialogClass m_ui; - QMap<QString, QStringList> m_filterMapBackup; - QMap<QString, QStringList> m_filterMap; - QStringList m_removedFilters; - QStringList m_docsBackup; - RegisteredDocsModel *m_registeredDocsModel; - QSortFilterProxyModel *m_registereredDocsFilterModel; - QStringList m_regDocs; - QStringList m_unregDocs; + + FilterSetup m_originalSetup; + FilterSetup m_currentSetup; + + QMap<QString, QListWidgetItem *> m_namespaceToItem; + QHash<QListWidgetItem *, QString> m_itemToNamespace; + + QMap<QString, QListWidgetItem *> m_filterToItem; + QHash<QListWidgetItem *, QString> m_itemToFilter; + FontPanel *m_appFontPanel; FontPanel *m_browserFontPanel; bool m_appFontChanged; diff --git a/src/assistant/assistant/preferencesdialog.ui b/src/assistant/assistant/preferencesdialog.ui index ebefa2458..68dbf68e2 100644 --- a/src/assistant/assistant/preferencesdialog.ui +++ b/src/assistant/assistant/preferencesdialog.ui @@ -6,24 +6,24 @@ <rect> <x>0</x> <y>0</y> - <width>375</width> - <height>342</height> + <width>395</width> + <height>341</height> </rect> </property> <property name="windowTitle"> <string>Preferences</string> </property> - <layout class="QVBoxLayout"> + <layout class="QVBoxLayout" name="verticalLayout_6"> <item> <widget class="QTabWidget" name="tabWidget"> <property name="currentIndex"> - <number>0</number> + <number>1</number> </property> <widget class="QWidget" name="fontsTab"> <attribute name="title"> <string>Fonts</string> </attribute> - <layout class="QGridLayout"> + <layout class="QGridLayout" name="gridLayout"> <item row="0" column="0"> <layout class="QHBoxLayout"> <item> @@ -69,123 +69,123 @@ <attribute name="title"> <string>Filters</string> </attribute> - <layout class="QGridLayout"> - <item row="0" column="0" colspan="2"> - <widget class="QLabel" name="label"> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="3"> + <widget class="QLabel" name="componentsLabel"> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> <property name="text"> - <string>Filter:</string> + <string>Components:</string> </property> </widget> </item> - <item row="0" column="2"> - <widget class="QLabel" name="label_2"> - <property name="frameShape"> - <enum>QFrame::NoFrame</enum> - </property> + <item row="0" column="4"> + <widget class="QLabel" name="versionsLabel"> <property name="text"> - <string>Attributes:</string> + <string>Versions:</string> </property> </widget> </item> - <item row="1" column="0" colspan="2"> + <item row="1" column="0" colspan="3"> <widget class="QListWidget" name="filterWidget"/> </item> - <item row="1" column="2" rowspan="2"> - <widget class="QTreeWidget" name="attributeWidget"> - <column> - <property name="text"> - <string>1</string> - </property> - </column> - </widget> + <item row="1" column="3" rowspan="2"> + <widget class="OptionsWidget" name="componentWidget" native="true"/> + </item> + <item row="1" column="4" rowspan="2"> + <widget class="OptionsWidget" name="versionWidget" native="true"/> </item> <item row="2" column="0"> - <widget class="QPushButton" name="filterAddButton"> + <widget class="QToolButton" name="filterAddButton"> <property name="text"> - <string>Add</string> + <string>Add...</string> </property> </widget> </item> <item row="2" column="1"> - <widget class="QPushButton" name="filterRemoveButton"> + <widget class="QToolButton" name="filterRenameButton"> + <property name="text"> + <string>Rename...</string> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QToolButton" name="filterRemoveButton"> <property name="text"> <string>Remove</string> </property> </widget> </item> + <item row="0" column="0" colspan="3"> + <widget class="QLabel" name="filterLabel"> + <property name="text"> + <string>Filter:</string> + </property> + </widget> + </item> </layout> </widget> <widget class="QWidget" name="docsTab"> <attribute name="title"> <string>Documentation</string> </attribute> - <layout class="QVBoxLayout" name="verticalLayout_5"> - <item> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0"> <widget class="QLabel" name="label_3"> <property name="text"> <string>Registered Documentation:</string> </property> </widget> </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item row="1" column="0"> + <widget class="QLineEdit" name="registeredDocsFilterLineEdit"> + <property name="placeholderText"> + <string><Filter></string> + </property> + <property name="clearButtonEnabled"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="1" rowspan="2"> + <layout class="QVBoxLayout" name="verticalLayout_4"> <item> - <layout class="QVBoxLayout" name="verticalLayout_4"> - <item> - <widget class="QLineEdit" name="registeredDocsFilterLineEdit"> - <property name="placeholderText"> - <string><Filter></string> - </property> - <property name="clearButtonEnabled"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QListView" name="registeredDocsListView"> - <property name="selectionMode"> - <enum>QAbstractItemView::ExtendedSelection</enum> - </property> - </widget> - </item> - </layout> + <widget class="QPushButton" name="docAddButton"> + <property name="text"> + <string>Add...</string> + </property> + </widget> </item> <item> - <layout class="QVBoxLayout"> - <property name="spacing"> - <number>6</number> + <widget class="QPushButton" name="docRemoveButton"> + <property name="text"> + <string>Remove</string> </property> - <item> - <widget class="QPushButton" name="docAddButton"> - <property name="text"> - <string>Add...</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="docRemoveButton"> - <property name="text"> - <string>Remove</string> - </property> - </widget> - </item> - <item> - <spacer> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - </layout> + </widget> + </item> + <item> + <spacer> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> </item> </layout> </item> + <item row="2" column="0"> + <widget class="QListWidget" name="registeredDocsListWidget"> + <property name="selectionMode"> + <enum>QAbstractItemView::ExtendedSelection</enum> + </property> + </widget> + </item> </layout> </widget> <widget class="QWidget" name="optionsTab"> @@ -348,38 +348,23 @@ </widget> </item> <item> - <layout class="QHBoxLayout"> - <property name="spacing"> - <number>6</number> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> </property> - <item> - <spacer> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> - </property> - </widget> - </item> - </layout> + </widget> </item> </layout> </widget> <layoutdefault spacing="6" margin="11"/> + <customwidgets> + <customwidget> + <class>OptionsWidget</class> + <extends>QWidget</extends> + <header>optionswidget.h</header> + <container>1</container> + </customwidget> + </customwidgets> <resources/> <connections> <connection> diff --git a/src/assistant/assistant/qtdocinstaller.cpp b/src/assistant/assistant/qtdocinstaller.cpp index 51b120136..b25aa2b72 100644 --- a/src/assistant/assistant/qtdocinstaller.cpp +++ b/src/assistant/assistant/qtdocinstaller.cpp @@ -63,7 +63,7 @@ void QtDocInstaller::installDocs() void QtDocInstaller::run() { TRACE_OBJ - m_qchDir = QLibraryInfo::location(QLibraryInfo::DocumentationPath); + m_qchDir.setPath(QLibraryInfo::location(QLibraryInfo::DocumentationPath)); m_qchFiles = m_qchDir.entryList(QStringList() << QLatin1String("*.qch")); bool changes = false; diff --git a/src/assistant/assistant/remotecontrol.cpp b/src/assistant/assistant/remotecontrol.cpp index cab7b5db6..7fdbee41e 100644 --- a/src/assistant/assistant/remotecontrol.cpp +++ b/src/assistant/assistant/remotecontrol.cpp @@ -42,6 +42,7 @@ #include <QtWidgets/QApplication> #include <QtHelp/QHelpEngine> +#include <QtHelp/QHelpFilterEngine> #include <QtHelp/QHelpIndexWidget> #include <QtHelp/QHelpSearchQueryWidget> @@ -223,12 +224,12 @@ void RemoteControl::handleExpandTocCommand(const QString &arg) void RemoteControl::handleSetCurrentFilterCommand(const QString &arg) { TRACE_OBJ - if (helpEngine.customFilters().contains(arg)) { + if (helpEngine.filterEngine()->filters().contains(arg)) { if (m_caching) { clearCache(); m_currentFilter = arg; } else { - helpEngine.setCurrentFilter(arg); + helpEngine.filterEngine()->setActiveFilter(arg); } } } @@ -270,7 +271,7 @@ void RemoteControl::applyCache() if (!links.isEmpty()) CentralWidget::instance()->setSource(links.first()); } else if (!m_currentFilter.isEmpty()) { - helpEngine.setCurrentFilter(m_currentFilter); + helpEngine.filterEngine()->setActiveFilter(m_currentFilter); } if (m_syncContents) diff --git a/src/assistant/assistant/searchwidget.cpp b/src/assistant/assistant/searchwidget.cpp index 136403d6f..f4d991df7 100644 --- a/src/assistant/assistant/searchwidget.cpp +++ b/src/assistant/assistant/searchwidget.cpp @@ -135,7 +135,7 @@ void SearchWidget::searchingStarted() void SearchWidget::searchingFinished(int searchResultCount) { TRACE_OBJ - Q_UNUSED(searchResultCount) + Q_UNUSED(searchResultCount); qApp->restoreOverrideCursor(); } diff --git a/src/assistant/help/help.pro b/src/assistant/help/help.pro index 7ffa008c5..cd7781dde 100644 --- a/src/assistant/help/help.pro +++ b/src/assistant/help/help.pro @@ -10,8 +10,12 @@ QMAKE_DOCS = $$PWD/doc/qthelp.qdocconf DEFINES -= QT_ASCII_CAST_WARNINGS RESOURCES += helpsystem.qrc -SOURCES += qhelpenginecore.cpp \ +SOURCES += \ + qcompressedhelpinfo.cpp \ + qhelpenginecore.cpp \ qhelpengine.cpp \ + qhelpfilterdata.cpp \ + qhelpfilterengine.cpp \ qhelpdbreader.cpp \ qhelpcontentwidget.cpp \ qhelpindexwidget.cpp \ @@ -24,9 +28,13 @@ SOURCES += qhelpenginecore.cpp \ qhelpsearchindexreader.cpp \ qhelp_global.cpp -HEADERS += qhelpenginecore.h \ +HEADERS += \ + qcompressedhelpinfo.h \ + qhelpenginecore.h \ qhelpengine.h \ qhelpengine_p.h \ + qhelpfilterdata.h \ + qhelpfilterengine.h \ qhelp_global.h \ qhelpdbreader_p.h \ qhelpcontentwidget.h \ diff --git a/src/assistant/help/qcompressedhelpinfo.cpp b/src/assistant/help/qcompressedhelpinfo.cpp new file mode 100644 index 000000000..bbdc64157 --- /dev/null +++ b/src/assistant/help/qcompressedhelpinfo.cpp @@ -0,0 +1,172 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "qcompressedhelpinfo.h" + +#include "qhelpdbreader_p.h" + +#include <QtCore/QThread> +#include <QtCore/QVersionNumber> + +QT_BEGIN_NAMESPACE + +class QCompressedHelpInfoPrivate : public QSharedData +{ +public: + QCompressedHelpInfoPrivate() = default; + QCompressedHelpInfoPrivate(const QCompressedHelpInfoPrivate &other) + : QSharedData(other) + , m_namespaceName(other.m_namespaceName) + , m_component(other.m_component) + , m_version(other.m_version) + { } + ~QCompressedHelpInfoPrivate() = default; + + QString m_namespaceName; + QString m_component; + QVersionNumber m_version; +}; + +/*! + \class QCompressedHelpInfo + \since 5.13 + \inmodule QtHelp + \brief The QCompressedHelpInfo class provides access to + the details about a compressed help file. + + The detailed information about the compressed + help file can be fetched by calling the fromCompressedHelpFile() + static method, providing the path to the compressed + help file. + + The class provides access to various information about a compressed help file. + The namespace associated with the given compressed help file is + namespaceName(), the associated component name is component() + and version() provides version information. + + \sa QHelpFilterEngine +*/ + +/*! + Constructs empty information about a compressed help file. +*/ +QCompressedHelpInfo::QCompressedHelpInfo() + : d(new QCompressedHelpInfoPrivate) +{ +} + +/*! + Constructs a copy of \a other. +*/ +QCompressedHelpInfo::QCompressedHelpInfo(const QCompressedHelpInfo &) = default; + +/*! + Move-constructs a QCompressedHelpInfo instance, + making it point to the same object that \a other was pointing to, + so that it contains the information the \a other used to contain. +*/ +QCompressedHelpInfo::QCompressedHelpInfo(QCompressedHelpInfo &&) = default; + +/*! + Destroys the QCompressedHelpInfo. +*/ +QCompressedHelpInfo::~QCompressedHelpInfo() = default; + +/*! + Makes this QHelpCollectionDetails into a copy of \a other, so the two + are identical, and returns a reference to this QHelpCollectionDetails. +*/ +QCompressedHelpInfo &QCompressedHelpInfo::operator=(const QCompressedHelpInfo &) = default; + +/*! + Move-assigns \a other to this QCompressedHelpInfo instance. +*/ +QCompressedHelpInfo &QCompressedHelpInfo::operator=(QCompressedHelpInfo &&) = default; + +/*! + \fn void QCompressedHelpInfo::swap(QCompressedHelpInfo &other) + + Swaps the compressed help file \a other with this compressed help file. This + operation is very fast and never fails. +*/ + +/*! + Returns the namespace name of the compressed help file. +*/ +QString QCompressedHelpInfo::namespaceName() const +{ + return d->m_namespaceName; +} + +/*! + Returns the component of the compressed help file. +*/ +QString QCompressedHelpInfo::component() const +{ + return d->m_component; +} + +/*! + Returns the version of the compressed help file. +*/ +QVersionNumber QCompressedHelpInfo::version() const +{ + return d->m_version; +} + +/*! + Returns the QCompressedHelpInfo instance for the + \a documentationFileName of the existing qch file. +*/ +QCompressedHelpInfo QCompressedHelpInfo::fromCompressedHelpFile(const QString &documentationFileName) +{ + QHelpDBReader reader(documentationFileName, + QHelpGlobal::uniquifyConnectionName(QLatin1String("GetCompressedHelpInfo"), + QThread::currentThread()), nullptr); + if (reader.init()) { + QCompressedHelpInfo info; + info.d->m_namespaceName = reader.namespaceName(); + info.d->m_component = reader.virtualFolder(); + info.d->m_version = QVersionNumber::fromString(reader.version()); + return info; + } + return QCompressedHelpInfo(); +} + +QT_END_NAMESPACE diff --git a/src/assistant/help/qcompressedhelpinfo.h b/src/assistant/help/qcompressedhelpinfo.h new file mode 100644 index 000000000..c392bb74c --- /dev/null +++ b/src/assistant/help/qcompressedhelpinfo.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +#ifndef QCOMPRESSEDHELPINFO_H +#define QCOMPRESSEDHELPINFO_H + +#include <QtHelp/qhelp_global.h> + +#include <QtCore/QSharedDataPointer> + +QT_BEGIN_NAMESPACE + +class QVersionNumber; +class QCompressedHelpInfoPrivate; + +class QHELP_EXPORT QCompressedHelpInfo final +{ +public: + QCompressedHelpInfo(); + QCompressedHelpInfo(const QCompressedHelpInfo &other); + QCompressedHelpInfo(QCompressedHelpInfo &&other); + ~QCompressedHelpInfo(); + + QCompressedHelpInfo &operator=(const QCompressedHelpInfo &other); + QCompressedHelpInfo &operator=(QCompressedHelpInfo &&other); + + void swap(QCompressedHelpInfo &other) Q_DECL_NOTHROW + { d.swap(other.d); } + + QString namespaceName() const; + QString component() const; + QVersionNumber version() const; + + static QCompressedHelpInfo fromCompressedHelpFile(const QString &documentationFileName); + +private: + QSharedDataPointer<QCompressedHelpInfoPrivate> d; +}; + +QT_END_NAMESPACE + +#endif // QHELPCOLLECTIONDETAILS_H diff --git a/src/assistant/help/qhelpcollectionhandler.cpp b/src/assistant/help/qhelpcollectionhandler.cpp index ddf5edccc..a7bea5494 100644 --- a/src/assistant/help/qhelpcollectionhandler.cpp +++ b/src/assistant/help/qhelpcollectionhandler.cpp @@ -40,6 +40,7 @@ #include "qhelpcollectionhandler_p.h" #include "qhelp_global.h" #include "qhelpdbreader_p.h" +#include "qhelpfilterdata.h" #include <QtCore/QDataStream> #include <QtCore/QDateTime> @@ -173,19 +174,34 @@ bool QHelpCollectionHandler::openCollectionFile() bool indexAndNamespaceFilterTablesMissing = false; - m_query->exec(QLatin1String("SELECT COUNT(*) FROM sqlite_master WHERE TYPE=\'table\' " - "AND (Name=\'IndexTable\' " - "OR Name=\'FileNameTable\' " - "OR Name=\'ContentsTable\' " - "OR Name=\'FileFilterTable\' " - "OR Name=\'IndexFilterTable\' " - "OR Name=\'ContentsFilterTable\' " - "OR Name=\'FileAttributeSetTable\' " - "OR Name=\'OptimizedFilterTable\' " - "OR Name=\'TimeStampTable\' " - "OR Name=\'VersionTable\')")); + const QStringList newTables = { + QLatin1String("IndexTable"), + QLatin1String("FileNameTable"), + QLatin1String("ContentsTable"), + QLatin1String("FileFilterTable"), + QLatin1String("IndexFilterTable"), + QLatin1String("ContentsFilterTable"), + QLatin1String("FileAttributeSetTable"), + QLatin1String("OptimizedFilterTable"), + QLatin1String("TimeStampTable"), + QLatin1String("VersionTable"), + QLatin1String("Filter"), + QLatin1String("ComponentTable"), + QLatin1String("ComponentMapping"), + QLatin1String("ComponentFilter"), + QLatin1String("VersionFilter") + }; + + QString queryString = QLatin1String("SELECT COUNT(*) " + "FROM sqlite_master " + "WHERE TYPE=\'table\'"); + queryString.append(QLatin1String(" AND (Name=\'")); + queryString.append(newTables.join(QLatin1String("\' OR Name=\'"))); + queryString.append(QLatin1String("\')")); + + m_query->exec(queryString); m_query->next(); - if (m_query->value(0).toInt() != 10) { + if (m_query->value(0).toInt() != newTables.count()) { if (!recreateIndexAndNamespaceFilterTables(m_query)) { emit error(tr("Cannot create index tables in file %1.").arg(collectionFile())); return false; @@ -198,7 +214,7 @@ bool QHelpCollectionHandler::openCollectionFile() const FileInfoList &docList = registeredDocumentations(); if (indexAndNamespaceFilterTablesMissing) { for (const QHelpCollectionHandler::FileInfo &info : docList) { - if (!registerIndexAndNamespaceFilterTables(info.namespaceName)) { + if (!registerIndexAndNamespaceFilterTables(info.namespaceName, true)) { emit error(tr("Cannot register index tables in file %1.").arg(collectionFile())); return false; } @@ -462,13 +478,18 @@ bool QHelpCollectionHandler::recreateIndexAndNamespaceFilterTables(QSqlQuery *qu << QLatin1String("DROP TABLE IF EXISTS FileNameTable") << QLatin1String("DROP TABLE IF EXISTS IndexTable") << QLatin1String("DROP TABLE IF EXISTS ContentsTable") - << QLatin1String("DROP TABLE IF EXISTS FileFilterTable") - << QLatin1String("DROP TABLE IF EXISTS IndexFilterTable") - << QLatin1String("DROP TABLE IF EXISTS ContentsFilterTable") - << QLatin1String("DROP TABLE IF EXISTS FileAttributeSetTable") - << QLatin1String("DROP TABLE IF EXISTS OptimizedFilterTable") + << QLatin1String("DROP TABLE IF EXISTS FileFilterTable") // legacy + << QLatin1String("DROP TABLE IF EXISTS IndexFilterTable") // legacy + << QLatin1String("DROP TABLE IF EXISTS ContentsFilterTable") // legacy + << QLatin1String("DROP TABLE IF EXISTS FileAttributeSetTable") // legacy + << QLatin1String("DROP TABLE IF EXISTS OptimizedFilterTable") // legacy << QLatin1String("DROP TABLE IF EXISTS TimeStampTable") << QLatin1String("DROP TABLE IF EXISTS VersionTable") + << QLatin1String("DROP TABLE IF EXISTS Filter") + << QLatin1String("DROP TABLE IF EXISTS ComponentTable") + << QLatin1String("DROP TABLE IF EXISTS ComponentMapping") + << QLatin1String("DROP TABLE IF EXISTS ComponentFilter") + << QLatin1String("DROP TABLE IF EXISTS VersionFilter") << QLatin1String("CREATE TABLE FileNameTable (" "FolderId INTEGER, " "Name TEXT, " @@ -509,7 +530,22 @@ bool QHelpCollectionHandler::recreateIndexAndNamespaceFilterTables(QSqlQuery *qu "TimeStamp TEXT)") << QLatin1String("CREATE TABLE VersionTable (" "NamespaceId INTEGER, " - "Version TEXT)"); + "Version TEXT)") + << QLatin1String("CREATE TABLE Filter (" + "FilterId INTEGER PRIMARY KEY, " + "Name TEXT)") + << QLatin1String("CREATE TABLE ComponentTable (" + "ComponentId INTEGER PRIMARY KEY, " + "Name TEXT)") + << QLatin1String("CREATE TABLE ComponentMapping (" + "ComponentId INTEGER, " + "NamespaceId INTEGER)") + << QLatin1String("CREATE TABLE ComponentFilter (" + "ComponentName TEXT, " + "FilterId INTEGER)") + << QLatin1String("CREATE TABLE VersionFilter (" + "Version TEXT, " + "FilterId INTEGER)"); for (const QString &q : tables) { if (!query->exec(q)) @@ -529,6 +565,189 @@ QStringList QHelpCollectionHandler::customFilters() const return list; } + +QStringList QHelpCollectionHandler::filters() const +{ + QStringList list; + if (m_query) { + m_query->exec(QLatin1String("SELECT Name FROM Filter ORDER BY Name")); + while (m_query->next()) + list.append(m_query->value(0).toString()); + } + return list; +} + +QStringList QHelpCollectionHandler::availableComponents() const +{ + QStringList list; + if (m_query) { + m_query->exec(QLatin1String("SELECT DISTINCT Name FROM ComponentTable ORDER BY Name")); + while (m_query->next()) + list.append(m_query->value(0).toString()); + } + return list; +} + +QStringList QHelpCollectionHandler::availableVersions() const +{ + QStringList list; + if (m_query) { + m_query->exec(QLatin1String("SELECT DISTINCT Version FROM VersionTable ORDER BY Version")); + while (m_query->next()) + list.append(m_query->value(0).toString()); + } + return list; +} + +QMap<QString, QString> QHelpCollectionHandler::namespaceToComponent() const +{ + QMap<QString, QString> result; + if (m_query) { + m_query->exec(QLatin1String("SELECT " + "NamespaceTable.Name, " + "ComponentTable.Name " + "FROM NamespaceTable, " + "ComponentTable, " + "ComponentMapping " + "WHERE NamespaceTable.Id = ComponentMapping.NamespaceId " + "AND ComponentMapping.ComponentId = ComponentTable.ComponentId")); + while (m_query->next()) + result.insert(m_query->value(0).toString(), m_query->value(1).toString()); + } + return result; +} + +QMap<QString, QVersionNumber> QHelpCollectionHandler::namespaceToVersion() const +{ + QMap<QString, QVersionNumber> result; + if (m_query) { + m_query->exec(QLatin1String("SELECT " + "NamespaceTable.Name, " + "VersionTable.Version " + "FROM NamespaceTable, " + "VersionTable " + "WHERE NamespaceTable.Id = VersionTable.NamespaceId")); + while (m_query->next()) { + result.insert(m_query->value(0).toString(), + QVersionNumber::fromString(m_query->value(1).toString())); + } + } + return result; +} + +QHelpFilterData QHelpCollectionHandler::filterData(const QString &filterName) const +{ + QStringList components; + QList<QVersionNumber> versions; + if (m_query) { + m_query->prepare(QLatin1String("SELECT ComponentFilter.ComponentName " + "FROM ComponentFilter, Filter " + "WHERE ComponentFilter.FilterId = Filter.FilterId " + "AND Filter.Name = ? " + "ORDER BY ComponentFilter.ComponentName")); + m_query->bindValue(0, filterName); + m_query->exec(); + while (m_query->next()) + components.append(m_query->value(0).toString()); + + m_query->prepare(QLatin1String("SELECT VersionFilter.Version " + "FROM VersionFilter, Filter " + "WHERE VersionFilter.FilterId = Filter.FilterId " + "AND Filter.Name = ? " + "ORDER BY VersionFilter.Version")); + m_query->bindValue(0, filterName); + m_query->exec(); + while (m_query->next()) + versions.append(QVersionNumber::fromString(m_query->value(0).toString())); + + } + QHelpFilterData data; + data.setComponents(components); + data.setVersions(versions); + return data; +} + +bool QHelpCollectionHandler::setFilterData(const QString &filterName, + const QHelpFilterData &filterData) +{ + if (!removeFilter(filterName)) + return false; + + m_query->prepare(QLatin1String("INSERT INTO Filter " + "VALUES (NULL, ?)")); + m_query->bindValue(0, filterName); + if (!m_query->exec()) + return false; + + const int filterId = m_query->lastInsertId().toInt(); + + QVariantList componentList; + QVariantList versionList; + QVariantList filterIdList; + + for (const QString &component : filterData.components()) { + componentList.append(component); + filterIdList.append(filterId); + } + + m_query->prepare(QLatin1String("INSERT INTO ComponentFilter " + "VALUES (?, ?)")); + m_query->addBindValue(componentList); + m_query->addBindValue(filterIdList); + if (!m_query->execBatch()) + return false; + + filterIdList.clear(); + for (const QVersionNumber &version : filterData.versions()) { + versionList.append(version.isNull() ? QString() : version.toString()); + filterIdList.append(filterId); + } + + m_query->prepare(QLatin1String("INSERT INTO VersionFilter " + "VALUES (?, ?)")); + m_query->addBindValue(versionList); + m_query->addBindValue(filterIdList); + if (!m_query->execBatch()) + return false; + + return true; +} + +bool QHelpCollectionHandler::removeFilter(const QString &filterName) +{ + m_query->prepare(QLatin1String("SELECT FilterId " + "FROM Filter " + "WHERE Name = ?")); + m_query->bindValue(0, filterName); + if (!m_query->exec()) + return false; + + if (!m_query->next()) + return true; // no filter in DB + + const int filterId = m_query->value(0).toInt(); + + m_query->prepare(QLatin1String("DELETE FROM Filter " + "WHERE Filter.Name = ?")); + m_query->bindValue(0, filterName); + if (!m_query->exec()) + return false; + + m_query->prepare(QLatin1String("DELETE FROM ComponentFilter " + "WHERE ComponentFilter.FilterId = ?")); + m_query->bindValue(0, filterId); + if (!m_query->exec()) + return false; + + m_query->prepare(QLatin1String("DELETE FROM VersionFilter " + "WHERE VersionFilter.FilterId = ?")); + m_query->bindValue(0, filterId); + if (!m_query->exec()) + return false; + + return true; +} + bool QHelpCollectionHandler::removeCustomFilter(const QString &filterName) { if (!isDBOpened() || filterName.isEmpty()) @@ -805,6 +1024,69 @@ bool QHelpCollectionHandler::fileExists(const QUrl &url) const return count; } +static QString prepareFilterQuery(const QString &filterName) +{ + if (filterName.isEmpty()) + return QString(); + + return QString::fromLatin1(" AND EXISTS(SELECT * FROM Filter WHERE Filter.Name = ?) " + "AND (" + "(NOT EXISTS(" // 1. filter by component + "SELECT * FROM " + "ComponentFilter, " + "Filter " + "WHERE ComponentFilter.FilterId = Filter.FilterId " + "AND Filter.Name = ?) " + "OR NamespaceTable.Id IN (" + "SELECT " + "NamespaceTable.Id " + "FROM " + "NamespaceTable, " + "ComponentTable, " + "ComponentMapping, " + "ComponentFilter, " + "Filter " + "WHERE ComponentMapping.NamespaceId = NamespaceTable.Id " + "AND ComponentTable.ComponentId = ComponentMapping.ComponentId " + "AND ((ComponentTable.Name = ComponentFilter.ComponentName) " + "OR (ComponentTable.Name IS NULL AND ComponentFilter.ComponentName IS NULL)) " + "AND ComponentFilter.FilterId = Filter.FilterId " + "AND Filter.Name = ?))" + " AND " + "(NOT EXISTS(" // 2. filter by version + "SELECT * FROM " + "VersionFilter, " + "Filter " + "WHERE VersionFilter.FilterId = Filter.FilterId " + "AND Filter.Name = ?) " + "OR NamespaceTable.Id IN (" + "SELECT " + "NamespaceTable.Id " + "FROM " + "NamespaceTable, " + "VersionFilter, " + "VersionTable, " + "Filter " + "WHERE VersionFilter.FilterId = Filter.FilterId " + "AND ((VersionFilter.Version = VersionTable.Version) " + "OR (VersionFilter.Version IS NULL AND VersionTable.Version IS NULL)) " + "AND VersionTable.NamespaceId = NamespaceTable.Id " + "AND Filter.Name = ?))" + ")"); +} + +static void bindFilterQuery(QSqlQuery *query, int bindStart, const QString &filterName) +{ + if (filterName.isEmpty()) + return; + + query->bindValue(bindStart, filterName); + query->bindValue(bindStart + 1, filterName); + query->bindValue(bindStart + 2, filterName); + query->bindValue(bindStart + 3, filterName); + query->bindValue(bindStart + 4, filterName); +} + static QString prepareFilterQuery(int attributesCount, const QString &idTableName, const QString &idColumnName, @@ -917,6 +1199,61 @@ QString QHelpCollectionHandler::namespaceForFile(const QUrl &url, return namespaceList.first(); } +QString QHelpCollectionHandler::namespaceForFile(const QUrl &url, + const QString &filterName) const +{ + if (!isDBOpened()) + return QString(); + + const FileInfo fileInfo = extractFileInfo(url); + if (fileInfo.namespaceName.isEmpty()) + return QString(); + + const QString filterlessQuery = QLatin1String( + "SELECT DISTINCT " + "NamespaceTable.Name " + "FROM " + "FileNameTable, " + "NamespaceTable, " + "FolderTable " + "WHERE FolderTable.Name = ? " + "AND FileNameTable.Name = ? " + "AND FileNameTable.FolderId = FolderTable.Id " + "AND FolderTable.NamespaceId = NamespaceTable.Id"); + + const QString filterQuery = filterlessQuery + + prepareFilterQuery(filterName); + + m_query->prepare(filterQuery); + m_query->bindValue(0, fileInfo.folderName); + m_query->bindValue(1, fileInfo.fileName); + bindFilterQuery(m_query, 2, filterName); + + if (!m_query->exec()) + return QString(); + + QVector<QString> namespaceList; + while (m_query->next()) + namespaceList.append(m_query->value(0).toString()); + + if (namespaceList.isEmpty()) + return QString(); + + if (namespaceList.contains(fileInfo.namespaceName)) + return fileInfo.namespaceName; + + const QString originalVersion = namespaceVersion(fileInfo.namespaceName); + + for (const QString &ns : namespaceList) { + const QString nsVersion = namespaceVersion(ns); + if (originalVersion == nsVersion) + return ns; + } + + // TODO: still, we may like to return the ns for the highest available version + return namespaceList.first(); +} + QStringList QHelpCollectionHandler::files(const QString &namespaceName, const QStringList &filterAttributes, const QString &extensionFilter) const @@ -967,6 +1304,53 @@ QStringList QHelpCollectionHandler::files(const QString &namespaceName, return fileNames; } +QStringList QHelpCollectionHandler::files(const QString &namespaceName, + const QString &filterName, + const QString &extensionFilter) const +{ + if (!isDBOpened()) + return QStringList(); + + const QString extensionQuery = extensionFilter.isEmpty() + ? QString() : QLatin1String(" AND FileNameTable.Name LIKE ?"); + const QString filterlessQuery = QLatin1String( + "SELECT " + "FolderTable.Name, " + "FileNameTable.Name " + "FROM " + "FileNameTable, " + "FolderTable, " + "NamespaceTable " + "WHERE FileNameTable.FolderId = FolderTable.Id " + "AND FolderTable.NamespaceId = NamespaceTable.Id " + "AND NamespaceTable.Name = ?") + extensionQuery; + + const QString filterQuery = filterlessQuery + + prepareFilterQuery(filterName); + + m_query->prepare(filterQuery); + m_query->bindValue(0, namespaceName); + int bindCount = 1; + if (!extensionFilter.isEmpty()) { + m_query->bindValue(bindCount, QString::fromLatin1("%.%1").arg(extensionFilter)); + ++bindCount; + } + + bindFilterQuery(m_query, bindCount, filterName); + + if (!m_query->exec()) + return QStringList(); + + QStringList fileNames; + while (m_query->next()) { + fileNames.append(m_query->value(0).toString() + + QLatin1Char('/') + + m_query->value(1).toString()); + } + + return fileNames; +} + QUrl QHelpCollectionHandler::findFile(const QUrl &url, const QStringList &filterAttributes) const { if (!isDBOpened()) @@ -981,12 +1365,26 @@ QUrl QHelpCollectionHandler::findFile(const QUrl &url, const QStringList &filter return result; } +QUrl QHelpCollectionHandler::findFile(const QUrl &url, const QString &filterName) const +{ + if (!isDBOpened()) + return QUrl(); + + const QString namespaceName = namespaceForFile(url, filterName); + if (namespaceName.isEmpty()) + return QUrl(); + + QUrl result = url; + result.setAuthority(namespaceName); + return result; +} + QByteArray QHelpCollectionHandler::fileData(const QUrl &url) const { if (!isDBOpened()) return QByteArray(); - const QString namespaceName = namespaceForFile(url); + const QString namespaceName = namespaceForFile(url, QString()); if (namespaceName.isEmpty()) return QByteArray(); @@ -1042,6 +1440,41 @@ QStringList QHelpCollectionHandler::indicesForFilter(const QStringList &filterAt return indices; } + +QStringList QHelpCollectionHandler::indicesForFilter(const QString &filterName) const +{ + QStringList indices; + + if (!isDBOpened()) + return indices; + + const QString filterlessQuery = QString::fromLatin1( + "SELECT DISTINCT " + "IndexTable.Name " + "FROM " + "IndexTable, " + "FileNameTable, " + "FolderTable, " + "NamespaceTable " + "WHERE IndexTable.FileId = FileNameTable.FileId " + "AND FileNameTable.FolderId = FolderTable.Id " + "AND IndexTable.NamespaceId = NamespaceTable.Id"); + + const QString filterQuery = filterlessQuery + + prepareFilterQuery(filterName) + + QLatin1String(" ORDER BY LOWER(IndexTable.Name), IndexTable.Name"); + + m_query->prepare(filterQuery); + bindFilterQuery(m_query, 0, filterName); + + m_query->exec(); + + while (m_query->next()) + indices.append(m_query->value(0).toString()); + + return indices; +} + static QString getTitle(const QByteArray &contents) { if (!contents.size()) @@ -1123,6 +1556,65 @@ QList<QHelpCollectionHandler::ContentsData> QHelpCollectionHandler::contentsForF return result; } +QList<QHelpCollectionHandler::ContentsData> QHelpCollectionHandler::contentsForFilter(const QString &filterName) const +{ + if (!isDBOpened()) + return QList<ContentsData>(); + + const QString filterlessQuery = QString::fromLatin1( + "SELECT DISTINCT " + "NamespaceTable.Name, " + "FolderTable.Name, " + "ContentsTable.Data, " + "VersionTable.Version " + "FROM " + "FolderTable, " + "NamespaceTable, " + "ContentsTable, " + "VersionTable " + "WHERE ContentsTable.NamespaceId = NamespaceTable.Id " + "AND NamespaceTable.Id = FolderTable.NamespaceId " + "AND ContentsTable.NamespaceId = NamespaceTable.Id " + "AND VersionTable.NamespaceId = NamespaceTable.Id"); + + const QString filterQuery = filterlessQuery + + prepareFilterQuery(filterName); + + m_query->prepare(filterQuery); + bindFilterQuery(m_query, 0, filterName); + + m_query->exec(); + + QMap<QString, QMap<QVersionNumber, ContentsData>> contentsMap; + + while (m_query->next()) { + const QString namespaceName = m_query->value(0).toString(); + const QByteArray contents = m_query->value(2).toByteArray(); + const QString versionString = m_query->value(3).toString(); + + const QString title = getTitle(contents); + const QVersionNumber version = QVersionNumber::fromString(versionString); + // get existing or insert a new one otherwise + ContentsData &contentsData = contentsMap[title][version]; + contentsData.namespaceName = namespaceName; + contentsData.folderName = m_query->value(1).toString(); + contentsData.contentsList.append(contents); + } + + QList<QHelpCollectionHandler::ContentsData> result; + for (const auto &versionContents : qAsConst(contentsMap)) { + // insert items in the reverse order of version number + const auto itBegin = versionContents.constBegin(); + auto it = versionContents.constEnd(); + while (it != itBegin) { + --it; + result.append(it.value()); + } + } + + return result; +} + bool QHelpCollectionHandler::removeCustomValue(const QString &key) { if (!isDBOpened()) @@ -1389,9 +1881,43 @@ int QHelpCollectionHandler::registerVirtualFolder(const QString &folderName, int emit error(tr("Cannot register virtual folder '%1'.").arg(folderName)); return -1; } + + if (registerComponent(folderName, namespaceId) < 0) + return -1; + return virtualId; } +int QHelpCollectionHandler::registerComponent(const QString &componentName, int namespaceId) +{ + m_query->prepare(QLatin1String("SELECT ComponentId FROM ComponentTable WHERE Name = ?")); + m_query->bindValue(0, componentName); + if (!m_query->exec()) + return -1; + + if (!m_query->next()) { + m_query->prepare(QLatin1String("INSERT INTO ComponentTable VALUES(NULL, ?)")); + m_query->bindValue(0, componentName); + if (!m_query->exec()) + return -1; + + m_query->prepare(QLatin1String("SELECT ComponentId FROM ComponentTable WHERE Name = ?")); + m_query->bindValue(0, componentName); + if (!m_query->exec() || !m_query->next()) + return -1; + } + + const int componentId = m_query->value(0).toInt(); + + m_query->prepare(QLatin1String("INSERT INTO ComponentMapping VALUES(?, ?)")); + m_query->bindValue(0, componentId); + m_query->bindValue(1, namespaceId); + if (!m_query->exec()) + return -1; + + return componentId; +} + bool QHelpCollectionHandler::registerVersion(const QString &version, int namespaceId) { if (!m_query) @@ -1405,7 +1931,8 @@ bool QHelpCollectionHandler::registerVersion(const QString &version, int namespa return m_query->exec(); } -bool QHelpCollectionHandler::registerIndexAndNamespaceFilterTables(const QString &nameSpace) +bool QHelpCollectionHandler::registerIndexAndNamespaceFilterTables( + const QString &nameSpace, bool createDefaultVersionFilter) { if (!isDBOpened()) return false; @@ -1419,13 +1946,14 @@ bool QHelpCollectionHandler::registerIndexAndNamespaceFilterTables(const QString const int nsId = m_query->value(0).toInt(); const QString fileName = m_query->value(1).toString(); - m_query->prepare(QLatin1String("SELECT Id FROM FolderTable WHERE NamespaceId=?")); + m_query->prepare(QLatin1String("SELECT Id, Name FROM FolderTable WHERE NamespaceId=?")); m_query->bindValue(0, nsId); m_query->exec(); if (!m_query->next()) return false; const int vfId = m_query->value(0).toInt(); + const QString vfName = m_query->value(1).toString(); const QString absFileName = absoluteDocPath(fileName); QHelpDBReader reader(absFileName, QHelpGlobal::uniquifyConnectionName( @@ -1433,6 +1961,7 @@ bool QHelpCollectionHandler::registerIndexAndNamespaceFilterTables(const QString if (!reader.init()) return false; + registerComponent(vfName, nsId); registerVersion(reader.version(), nsId); if (!registerFileAttributeSets(reader.filterAttributeSets(), nsId)) return false; @@ -1440,9 +1969,30 @@ bool QHelpCollectionHandler::registerIndexAndNamespaceFilterTables(const QString if (!registerIndexTable(reader.indexTable(), nsId, vfId, fileName)) return false; + if (createDefaultVersionFilter) + createVersionFilter(reader.version()); + return true; } +void QHelpCollectionHandler::createVersionFilter(const QString &version) +{ + if (version.isEmpty()) + return; + + const QVersionNumber versionNumber = QVersionNumber::fromString(version); + if (versionNumber.isNull()) + return; + + const QString filterName = tr("Version %1").arg(version); + if (filters().contains(filterName)) + return; + + QHelpFilterData filterData; + filterData.setVersions(QList<QVersionNumber>() << versionNumber); + setFilterData(filterName, filterData); +} + bool QHelpCollectionHandler::registerIndexTable(const QHelpDBReader::IndexTable &indexTable, int nsId, int vfId, const QString &fileName) { @@ -1707,6 +2257,33 @@ bool QHelpCollectionHandler::unregisterIndexTable(int nsId, int vfId) if (!m_query->exec()) return false; + m_query->prepare(QLatin1String("SELECT ComponentId FROM ComponentMapping WHERE NamespaceId = ?")); + m_query->bindValue(0, nsId); + if (!m_query->exec()) + return false; + + if (!m_query->next()) + return false; + + const int componentId = m_query->value(0).toInt(); + + m_query->prepare(QLatin1String("DELETE FROM ComponentMapping WHERE NamespaceId = ?")); + m_query->bindValue(0, nsId); + if (!m_query->exec()) + return false; + + m_query->prepare(QLatin1String("SELECT ComponentId FROM ComponentMapping WHERE ComponentId = ?")); + m_query->bindValue(0, componentId); + if (!m_query->exec()) + return false; + + if (!m_query->next()) { // no more namespaces refer to the componentId + m_query->prepare(QLatin1String("DELETE FROM ComponentTable WHERE ComponentId = ?")); + m_query->bindValue(0, componentId); + if (!m_query->exec()) + return false; + } + return true; } @@ -1785,6 +2362,94 @@ QMap<QString, QUrl> QHelpCollectionHandler::linksForField(const QString &fieldNa return linkMap; } +QMap<QString, QUrl> QHelpCollectionHandler::linksForIdentifier(const QString &id, + const QString &filterName) const +{ + return linksForField(QLatin1String("Identifier"), id, filterName); +} + +QMap<QString, QUrl> QHelpCollectionHandler::linksForKeyword(const QString &keyword, + const QString &filterName) const +{ + return linksForField(QLatin1String("Name"), keyword, filterName); +} + +QMap<QString, QUrl> QHelpCollectionHandler::linksForField(const QString &fieldName, + const QString &fieldValue, + const QString &filterName) const +{ + QMap<QString, QUrl> linkMap; + + if (!isDBOpened()) + return linkMap; + + const QString filterlessQuery = QString::fromLatin1( + "SELECT " + "FileNameTable.Title, " + "NamespaceTable.Name, " + "FolderTable.Name, " + "FileNameTable.Name, " + "IndexTable.Anchor " + "FROM " + "IndexTable, " + "FileNameTable, " + "FolderTable, " + "NamespaceTable " + "WHERE IndexTable.FileId = FileNameTable.FileId " + "AND FileNameTable.FolderId = FolderTable.Id " + "AND IndexTable.NamespaceId = NamespaceTable.Id " + "AND IndexTable.%1 = ?").arg(fieldName); + + const QString filterQuery = filterlessQuery + + prepareFilterQuery(filterName); + + m_query->prepare(filterQuery); + m_query->bindValue(0, fieldValue); + bindFilterQuery(m_query, 1, filterName); + + m_query->exec(); + + while (m_query->next()) { + QString title = m_query->value(0).toString(); + if (title.isEmpty()) // generate a title + corresponding path + title = fieldValue + QLatin1String(" : ") + m_query->value(3).toString(); + + linkMap.insertMulti(title, buildQUrl(m_query->value(1).toString(), + m_query->value(2).toString(), + m_query->value(3).toString(), + m_query->value(4).toString())); + } + return linkMap; +} + +QStringList QHelpCollectionHandler::namespacesForFilter(const QString &filterName) const +{ + QStringList namespaceList; + + if (!isDBOpened()) + return namespaceList; + + const QString filterlessQuery = QString::fromLatin1( + "SELECT " + "NamespaceTable.Name " + "FROM " + "NamespaceTable " + "WHERE TRUE"); + + const QString filterQuery = filterlessQuery + + prepareFilterQuery(filterName); + + m_query->prepare(filterQuery); + bindFilterQuery(m_query, 0, filterName); + + m_query->exec(); + + while (m_query->next()) + namespaceList.append(m_query->value(0).toString()); + + return namespaceList; +} + void QHelpCollectionHandler::setReadOnly(bool readOnly) { m_readOnly = readOnly; diff --git a/src/assistant/help/qhelpcollectionhandler_p.h b/src/assistant/help/qhelpcollectionhandler_p.h index a0a8ace7a..7679fccf7 100644 --- a/src/assistant/help/qhelpcollectionhandler_p.h +++ b/src/assistant/help/qhelpcollectionhandler_p.h @@ -63,6 +63,9 @@ QT_BEGIN_NAMESPACE +class QVersionNumber; +class QHelpFilterData; + class QHelpCollectionHandler : public QObject { Q_OBJECT @@ -101,45 +104,103 @@ public: bool openCollectionFile(); bool copyCollectionFile(const QString &fileName); + // *** Legacy block start *** + // legacy API since Qt 5.13 + + // use filters() instead QStringList customFilters() const; + + // use QHelpFilterEngine::removeFilter() instead bool removeCustomFilter(const QString &filterName); + + // use QHelpFilterEngine::setFilterData() instead bool addCustomFilter(const QString &filterName, const QStringList &attributes); + // use files(const QString &, const QString &, const QString &) instead + QStringList files(const QString &namespaceName, + const QStringList &filterAttributes, + const QString &extensionFilter) const; + + // use namespaceForFile(const QUrl &, const QString &) instead + QString namespaceForFile(const QUrl &url, + const QStringList &filterAttributes) const; + + // use findFile(const QUrl &, const QString &) instead + QUrl findFile(const QUrl &url, + const QStringList &filterAttributes) const; + + // use indicesForFilter(const QString &) instead + QStringList indicesForFilter(const QStringList &filterAttributes) const; + + // use contentsForFilter(const QString &) instead + QList<ContentsData> contentsForFilter(const QStringList &filterAttributes) const; + + // use QHelpFilterEngine::activeFilter() and filterData(const QString &) instead; + QStringList filterAttributes() const; + + // use filterData(const QString &) instead + QStringList filterAttributes(const QString &filterName) const; + + // use filterData(const QString &) instead + QList<QStringList> filterAttributeSets(const QString &namespaceName) const; + + // use linksForIdentifier(const QString &, const QString &) instead + QMap<QString, QUrl> linksForIdentifier(const QString &id, + const QStringList &filterAttributes) const; + + // use linksForKeyword(const QString &, const QString &) instead + QMap<QString, QUrl> linksForKeyword(const QString &keyword, + const QStringList &filterAttributes) const; + + // *** Legacy block end *** + + QStringList filters() const; + + QStringList availableComponents() const; + QStringList availableVersions() const; + QMap<QString, QString> namespaceToComponent() const; + QMap<QString, QVersionNumber> namespaceToVersion() const; + QHelpFilterData filterData(const QString &filterName) const; + bool setFilterData(const QString &filterName, const QHelpFilterData &filterData); + bool removeFilter(const QString &filterName); + + FileInfo registeredDocumentation(const QString &namespaceName) const; FileInfoList registeredDocumentations() const; bool registerDocumentation(const QString &fileName); bool unregisterDocumentation(const QString &namespaceName); + bool fileExists(const QUrl &url) const; QStringList files(const QString &namespaceName, - const QStringList &filterAttributes = QStringList(), - const QString &extensionFilter = QString()) const; + const QString &filterName, + const QString &extensionFilter) const; QString namespaceForFile(const QUrl &url, - const QStringList &filterAttributes = QStringList()) const; + const QString &filterName) const; QUrl findFile(const QUrl &url, - const QStringList &filterAttributes = QStringList()) const; + const QString &filterName) const; QByteArray fileData(const QUrl &url) const; - QStringList indicesForFilter(const QStringList &filterAttributes) const; - QList<ContentsData> contentsForFilter(const QStringList &filterAttributes) const; + + QStringList indicesForFilter(const QString &filterName) const; + QList<ContentsData> contentsForFilter(const QString &filterName) const; bool removeCustomValue(const QString &key); QVariant customValue(const QString &key, const QVariant &defaultValue) const; bool setCustomValue(const QString &key, const QVariant &value); - QStringList filterAttributes() const; - QStringList filterAttributes(const QString &filterName) const; - QList<QStringList> filterAttributeSets(const QString &namespaceName) const; int registerNamespace(const QString &nspace, const QString &fileName); int registerVirtualFolder(const QString &folderName, int namespaceId); + int registerComponent(const QString &componentName, int namespaceId); bool registerVersion(const QString &version, int namespaceId); QMap<QString, QUrl> linksForIdentifier(const QString &id, - const QStringList &filterAttributes) const; + const QString &filterName) const; QMap<QString, QUrl> linksForKeyword(const QString &keyword, - const QStringList &filterAttributes) const; + const QString &filterName) const; + QStringList namespacesForFilter(const QString &filterName) const; void setReadOnly(bool readOnly); @@ -147,15 +208,22 @@ signals: void error(const QString &msg) const; private: - QString namespaceVersion(const QString &namespaceName) const; + // legacy stuff QMap<QString, QUrl> linksForField(const QString &fieldName, const QString &fieldValue, const QStringList &filterAttributes) const; + + QString namespaceVersion(const QString &namespaceName) const; + QMap<QString, QUrl> linksForField(const QString &fieldName, + const QString &fieldValue, + const QString &filterName) const; bool isDBOpened() const; bool createTables(QSqlQuery *query); void closeDB(); bool recreateIndexAndNamespaceFilterTables(QSqlQuery *query); - bool registerIndexAndNamespaceFilterTables(const QString &nameSpace); + bool registerIndexAndNamespaceFilterTables(const QString &nameSpace, + bool createDefaultVersionFilter = false); + void createVersionFilter(const QString &version); bool registerFilterAttributes(const QList<QStringList> &attributeSets, int nsId); bool registerFileAttributeSets(const QList<QStringList> &attributeSets, int nsId); bool registerIndexTable(const QHelpDBReader::IndexTable &indexTable, diff --git a/src/assistant/help/qhelpcontentwidget.cpp b/src/assistant/help/qhelpcontentwidget.cpp index c099d1765..f0702602e 100644 --- a/src/assistant/help/qhelpcontentwidget.cpp +++ b/src/assistant/help/qhelpcontentwidget.cpp @@ -76,18 +76,18 @@ public: ~QHelpContentProvider() override; void collectContents(const QString &customFilterName); void stopCollecting(); - QHelpContentItem *rootItem(); - -signals: - void finishedSuccessFully(); + QHelpContentItem *takeContentItem(); private: void run() override; QHelpEnginePrivate *m_helpEngine; + QString m_currentFilter; QStringList m_filterAttributes; - QQueue<QHelpContentItem*> m_rootItems; + QString m_collectionFile; + QHelpContentItem *m_rootItem = nullptr; QMutex m_mutex; + bool m_usesFilterEngine = false; bool m_abort = false; }; @@ -197,14 +197,15 @@ QHelpContentProvider::~QHelpContentProvider() 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()) { - start(LowPriority); - } else { + + if (isRunning()) stopCollecting(); - start(LowPriority); - } + start(LowPriority); } void QHelpContentProvider::stopCollecting() @@ -220,16 +221,16 @@ void QHelpContentProvider::stopCollecting() // either way never resetting m_abort to false from within the run() method m_abort = false; } - qDeleteAll(m_rootItems); - m_rootItems.clear(); + delete m_rootItem; + m_rootItem = nullptr; } -QHelpContentItem *QHelpContentProvider::rootItem() +QHelpContentItem *QHelpContentProvider::takeContentItem() { QMutexLocker locker(&m_mutex); - if (m_rootItems.isEmpty()) - return nullptr; - return m_rootItems.dequeue(); + QHelpContentItem *content = m_rootItem; + m_rootItem = nullptr; + return content; } // TODO: this is a copy from helpcollectionhandler, make it common @@ -263,8 +264,12 @@ void QHelpContentProvider::run() 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_helpEngine->collectionHandler->collectionFile(); + const QString collectionFile = m_collectionFile; + const bool usesFilterEngine = m_usesFilterEngine; + delete m_rootItem; + m_rootItem = nullptr; m_mutex.unlock(); if (collectionFile.isEmpty()) @@ -274,8 +279,9 @@ void QHelpContentProvider::run() if (!collectionHandler.openCollectionFile()) return; - const QList<QHelpCollectionHandler::ContentsData> result - = collectionHandler.contentsForFilter(attributes); + const QList<QHelpCollectionHandler::ContentsData> result = usesFilterEngine + ? collectionHandler.contentsForFilter(currentFilter) + : collectionHandler.contentsForFilter(attributes); for (const auto &contentsData : result) { m_mutex.lock(); @@ -333,10 +339,9 @@ CHECK_DEPTH: } m_mutex.lock(); - m_rootItems.enqueue(rootItem); + m_rootItem = rootItem; m_abort = false; m_mutex.unlock(); - emit finishedSuccessFully(); } /*! @@ -368,10 +373,8 @@ QHelpContentModel::QHelpContentModel(QHelpEnginePrivate *helpEngine) d = new QHelpContentModelPrivate(); d->qhelpContentProvider = new QHelpContentProvider(helpEngine); - connect(d->qhelpContentProvider, &QHelpContentProvider::finishedSuccessFully, - this, &QHelpContentModel::insertContents, Qt::QueuedConnection); - connect(helpEngine->q, &QHelpEngineCore::readersAboutToBeInvalidated, - this, [this]() { invalidateContents(); }); + connect(d->qhelpContentProvider, &QThread::finished, + this, &QHelpContentModel::insertContents); } /*! @@ -383,36 +386,32 @@ QHelpContentModel::~QHelpContentModel() delete d; } -void QHelpContentModel::invalidateContents(bool onShutDown) -{ - if (onShutDown) { - disconnect(d->qhelpContentProvider, &QHelpContentProvider::finishedSuccessFully, - this, &QHelpContentModel::insertContents); - } else { - beginResetModel(); - } - d->qhelpContentProvider->stopCollecting(); - if (d->rootItem) { - delete d->rootItem; - d->rootItem = nullptr; - } - if (!onShutDown) - endResetModel(); -} - /*! 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() { - QHelpContentItem * const newRootItem = d->qhelpContentProvider->rootItem(); + if (d->qhelpContentProvider->isRunning()) + return; + + QHelpContentItem * const newRootItem = d->qhelpContentProvider->takeContentItem(); if (!newRootItem) return; beginResetModel(); @@ -497,7 +496,7 @@ int QHelpContentModel::rowCount(const QModelIndex &parent) const */ int QHelpContentModel::columnCount(const QModelIndex &parent) const { - Q_UNUSED(parent) + Q_UNUSED(parent); return 1; } diff --git a/src/assistant/help/qhelpcontentwidget.h b/src/assistant/help/qhelpcontentwidget.h index 01594d60f..b316ebba9 100644 --- a/src/assistant/help/qhelpcontentwidget.h +++ b/src/assistant/help/qhelpcontentwidget.h @@ -100,7 +100,6 @@ Q_SIGNALS: private Q_SLOTS: void insertContents(); - void invalidateContents(bool onShutDown = false); private: QHelpContentModel(QHelpEnginePrivate *helpEngine); diff --git a/src/assistant/help/qhelpengine.cpp b/src/assistant/help/qhelpengine.cpp index 966c33354..19e44cd91 100644 --- a/src/assistant/help/qhelpengine.cpp +++ b/src/assistant/help/qhelpengine.cpp @@ -44,6 +44,7 @@ #include "qhelpindexwidget.h" #include "qhelpsearchengine.h" #include "qhelpcollectionhandler_p.h" +#include "qhelpfilterengine.h" #include <QtCore/QDir> #include <QtCore/QFile> @@ -68,6 +69,8 @@ void QHelpEnginePrivate::init(const QString &collectionFile, this, &QHelpEnginePrivate::scheduleApplyCurrentFilter); connect(helpEngineCore, &QHelpEngineCore::currentFilterChanged, this, &QHelpEnginePrivate::scheduleApplyCurrentFilter); + connect(helpEngineCore->filterEngine(), &QHelpFilterEngine::filterActivated, + this, &QHelpEnginePrivate::scheduleApplyCurrentFilter); } void QHelpEnginePrivate::scheduleApplyCurrentFilter() @@ -85,8 +88,11 @@ void QHelpEnginePrivate::scheduleApplyCurrentFilter() void QHelpEnginePrivate::applyCurrentFilter() { m_isApplyCurrentFilterScheduled = false; - contentModel->createContents(currentFilter); - indexModel->createIndex(currentFilter); + const QString filter = usesFilterEngine + ? q->filterEngine()->activeFilter() + : currentFilter; + contentModel->createContents(filter); + indexModel->createIndex(filter); } void QHelpEnginePrivate::setContentsWidgetBusy() @@ -117,22 +123,12 @@ void QHelpEnginePrivate::unsetIndexWidgetBusy() #endif } -void QHelpEnginePrivate::stopDataCollection() -{ - contentModel->invalidateContents(true); - indexModel->invalidateIndex(true); -} - - - /*! \class QHelpEngine \since 4.4 \inmodule QtHelp \brief The QHelpEngine class provides access to contents and indices of the help engine. - - */ /*! @@ -152,7 +148,6 @@ QHelpEngine::QHelpEngine(const QString &collectionFile, QObject *parent) */ QHelpEngine::~QHelpEngine() { - d->stopDataCollection(); } /*! diff --git a/src/assistant/help/qhelpengine_p.h b/src/assistant/help/qhelpengine_p.h index b1f986d20..558013e77 100644 --- a/src/assistant/help/qhelpengine_p.h +++ b/src/assistant/help/qhelpengine_p.h @@ -68,6 +68,7 @@ class QHelpIndexModel; class QHelpIndexWidget; class QHelpSearchEngine; class QHelpCollectionHandler; +class QHelpFilterEngine; class QHelpEngineCorePrivate : public QObject { @@ -79,14 +80,15 @@ public: virtual void init(const QString &collectionFile, QHelpEngineCore *helpEngineCore); - void emitReadersAboutToBeInvalidated(); bool setup(); QHelpCollectionHandler *collectionHandler = nullptr; + QHelpFilterEngine *filterEngine = nullptr; QString currentFilter; QString error; bool needsSetup = true; bool autoSaveFilter = true; + bool usesFilterEngine = false; protected: QHelpEngineCore *q; @@ -112,8 +114,6 @@ public: QHelpSearchEngine *searchEngine = nullptr; - void stopDataCollection(); - friend class QHelpContentProvider; friend class QHelpContentModel; friend class QHelpIndexProvider; diff --git a/src/assistant/help/qhelpenginecore.cpp b/src/assistant/help/qhelpenginecore.cpp index 0e8535415..f61c2207d 100644 --- a/src/assistant/help/qhelpenginecore.cpp +++ b/src/assistant/help/qhelpenginecore.cpp @@ -41,6 +41,7 @@ #include "qhelpengine_p.h" #include "qhelpdbreader_p.h" #include "qhelpcollectionhandler_p.h" +#include "qhelpfilterengine.h" #include <QtCore/QDir> #include <QtCore/QFile> @@ -59,18 +60,13 @@ void QHelpEngineCorePrivate::init(const QString &collectionFile, collectionHandler = new QHelpCollectionHandler(collectionFile, helpEngineCore); connect(collectionHandler, &QHelpCollectionHandler::error, this, &QHelpEngineCorePrivate::errorReceived); + filterEngine->setCollectionHandler(collectionHandler); needsSetup = true; } QHelpEngineCorePrivate::~QHelpEngineCorePrivate() { delete collectionHandler; - emitReadersAboutToBeInvalidated(); -} - -void QHelpEngineCorePrivate::emitReadersAboutToBeInvalidated() -{ - emit q->readersAboutToBeInvalidated(); } bool QHelpEngineCorePrivate::setup() @@ -81,7 +77,6 @@ bool QHelpEngineCorePrivate::setup() needsSetup = false; emit q->setupStarted(); - emitReadersAboutToBeInvalidated(); const QVariant readOnlyVariant = q->property("_q_readonly"); const bool readOnly = readOnlyVariant.isValid() @@ -122,11 +117,14 @@ void QHelpEngineCorePrivate::errorReceived(const QString &msg) depends on the currently set custom filter. Depending on the filter, the function may return different results. - Every help engine can contain any number of custom filters. A custom - filter is defined by a name and set of filter attributes and can be - added to the help engine by calling addCustomFilter(). Analogous, - it is removed by calling removeCustomFilter(). customFilters() returns - all defined filters. + The help engine can contain any number of custom filters. + The management of the filters, including adding new filters, + changing filter definitions, or removing existing filters, + is done through the QHelpFilterEngine class, which can be accessed + by the filterEngine() method. This replaces older filter API that is + deprecated since Qt 5.13. Please call setUsesFilterEngine() with + \c true to enable the new functionality. + The help engine also offers the possibility to set and read values in a persistant way comparable to ini files or Windows registry @@ -157,7 +155,15 @@ void QHelpEngineCorePrivate::errorReceived(const QString &msg) */ /*! + \fn void QHelpEngineCore::readersAboutToBeInvalidated() + \obsolete +*/ + +/*! \fn void QHelpEngineCore::currentFilterChanged(const QString &newFilter) + \obsolete + + QHelpFilterEngine::filterActivated() should be used instead. This signal is emitted when the current filter is changed to \a newFilter. @@ -179,6 +185,7 @@ QHelpEngineCore::QHelpEngineCore(const QString &collectionFile, QObject *parent) : QObject(parent) { d = new QHelpEngineCorePrivate(); + d->filterEngine = new QHelpFilterEngine(this); d->init(collectionFile, this); } @@ -190,6 +197,7 @@ QHelpEngineCore::QHelpEngineCore(QHelpEngineCorePrivate *helpEngineCorePrivate, : QObject(parent) { d = helpEngineCorePrivate; + d->filterEngine = new QHelpFilterEngine(this); } /*! @@ -222,13 +230,25 @@ void QHelpEngineCore::setCollectionFile(const QString &fileName) if (d->collectionHandler) { delete d->collectionHandler; d->collectionHandler = nullptr; - d->emitReadersAboutToBeInvalidated(); } d->init(fileName, this); d->needsSetup = true; } /*! + \since 5.13 + + Returns the filter engine associated with this help engine. + The filter engine allows for adding, changing, and removing existing + filters for this help engine. To use the engine you also have to call + \l setUsesFilterEngine() set to \c true. +*/ +QHelpFilterEngine *QHelpEngineCore::filterEngine() const +{ + return d->filterEngine; +} + +/*! Sets up the help engine by processing the information found in the collection file and returns true if successful; otherwise returns false. @@ -352,6 +372,10 @@ QStringList QHelpEngineCore::registeredDocumentations() const } /*! + \obsolete + + QHelpFilterEngine::filters() should be used instead. + Returns a list of custom filters. \sa addCustomFilter(), removeCustomFilter() @@ -364,6 +388,10 @@ QStringList QHelpEngineCore::customFilters() const } /*! + \obsolete + + QHelpFilterEngine::setFilterData() should be used instead. + Adds the new custom filter \a filterName. The filter attributes are specified by \a attributes. If the filter already exists, its attribute set is replaced. The function returns true if @@ -380,6 +408,10 @@ bool QHelpEngineCore::addCustomFilter(const QString &filterName, } /*! + \obsolete + + QHelpFilterEngine::removeFilter() should be used instead. + Returns true if the filter \a filterName was removed successfully, otherwise false. @@ -393,6 +425,10 @@ bool QHelpEngineCore::removeCustomFilter(const QString &filterName) } /*! + \obsolete + + QHelpFilterEngine::availableComponents() should be used instead. + Returns a list of all defined filter attributes. */ QStringList QHelpEngineCore::filterAttributes() const @@ -403,6 +439,10 @@ QStringList QHelpEngineCore::filterAttributes() const } /*! + \obsolete + + QHelpFilterEngine::filterData() should be used instead. + Returns a list of filter attributes used by the custom filter \a filterName. */ @@ -414,10 +454,13 @@ QStringList QHelpEngineCore::filterAttributes(const QString &filterName) const } /*! + \obsolete \property QHelpEngineCore::currentFilter \brief the name of the custom filter currently applied. \since 4.5 + QHelpFilterEngine::activeFilter() should be used instead. + Setting this property will save the new custom filter permanently in the help collection file. To set a custom filter without saving it permanently, disable the auto save filter mode. @@ -453,6 +496,10 @@ void QHelpEngineCore::setCurrentFilter(const QString &filterName) } /*! + \obsolete + + QHelpFilterEngine::filterData() should be used instead. + Returns a list of filter attributes for the different filter sections defined in the Qt compressed help file with the given namespace \a namespaceName. @@ -466,6 +513,10 @@ QList<QStringList> QHelpEngineCore::filterAttributeSets(const QString &namespace } /*! + \obsolete + + files() should be used instead. + Returns a list of files contained in the Qt compressed help file \a namespaceName. The files can be filtered by \a filterAttributes as well as by their extension \a extensionFilter (e.g. 'html'). @@ -492,22 +543,55 @@ QList<QUrl> QHelpEngineCore::files(const QString namespaceName, } /*! - Returns an invalid URL if the file \a url cannot be found. - If the file exists, either the same url is returned or a - different url if the file is located in a different namespace - which is merged via a common virtual folder. + Returns a list of files contained in the Qt compressed help file + for \a namespaceName. The files can be filtered by \a filterName as + well as by their extension \a extensionFilter (for example, 'html'). +*/ +QList<QUrl> QHelpEngineCore::files(const QString namespaceName, + const QString &filterName, + const QString &extensionFilter) +{ + QList<QUrl> res; + if (!d->setup()) + return res; + + QUrl url; + url.setScheme(QLatin1String("qthelp")); + url.setAuthority(namespaceName); + + const QStringList &files = d->collectionHandler->files( + namespaceName, filterName, extensionFilter); + for (const QString &file : files) { + url.setPath(QLatin1String("/") + file); + res.append(url); + } + return res; +} + +/*! + Returns the corrected URL for the \a url that may refer to + a different namespace defined by the virtual folder defined + as a part of the \a url. If the virtual folder matches the namespace + of the \a url, the method just checks if the file exists and returns + the same \a url. When the virtual folder doesn't match the namespace + of the \a url, it tries to find the best matching namespace according + to the active filter. When the namespace is found, it returns the + corrected URL if the file exists, otherwise it returns an invalid URL. */ QUrl QHelpEngineCore::findFile(const QUrl &url) const { if (!d->setup()) return url; - const QStringList &attributes = filterAttributes(currentFilter()); - QUrl result = d->collectionHandler->findFile(url, attributes); + QUrl result = d->usesFilterEngine + ? d->collectionHandler->findFile(url, d->filterEngine->activeFilter()) + : d->collectionHandler->findFile(url, filterAttributes(currentFilter())); // obsolete if (!result.isEmpty()) return result; - result = d->collectionHandler->findFile(url); + result = d->usesFilterEngine + ? d->collectionHandler->findFile(url, QString()) + : d->collectionHandler->findFile(url, QStringList()); // obsolete if (!result.isEmpty()) return result; @@ -529,30 +613,37 @@ QByteArray QHelpEngineCore::fileData(const QUrl &url) const } /*! - Returns documents found for the \a id. The map contains the - document titles and their URLs. - The returned map contents depends on the current filter, meaning only the keywords - registered for the current filter will be returned. + Returns a map of the documents found for the \a id. The map contains the + document titles and their URLs. The returned map contents depend on + the current filter, and therefore only the identifiers registered for + the current filter will be returned. */ QMap<QString, QUrl> QHelpEngineCore::linksForIdentifier(const QString &id) const { if (!d->setup()) return QMap<QString, QUrl>(); + if (d->usesFilterEngine) + return d->collectionHandler->linksForIdentifier(id, d->filterEngine->activeFilter()); + + // obsolete return d->collectionHandler->linksForIdentifier(id, filterAttributes(d->currentFilter)); } /*! - \since 4.5 - - Returns all documents found for the \a keyword. The returned map consists of the - document titles and their URLs. + Returns a map of all the documents found for the \a keyword. The map + contains the document titles and URLs. The returned map contents depend + on the current filter, and therefore only the keywords registered for + the current filter will be returned. */ QMap<QString, QUrl> QHelpEngineCore::linksForKeyword(const QString &keyword) const { if (!d->setup()) return QMap<QString, QUrl>(); + if (d->usesFilterEngine) + return d->collectionHandler->linksForKeyword(keyword, d->filterEngine->activeFilter()); + return d->collectionHandler->linksForKeyword(keyword, filterAttributes(d->currentFilter)); } @@ -628,7 +719,7 @@ QString QHelpEngineCore::error() const \since 4.5 If QHelpEngineCore is in auto save filter mode, the current filter is - automatically saved when it is changed by the setCurrentFilter() + automatically saved when it is changed by the QHelpFilterEngine::setActiveFilter() function. The filter is saved persistently in the help collection file. By default, this mode is on. @@ -643,4 +734,29 @@ bool QHelpEngineCore::autoSaveFilter() const return d->autoSaveFilter; } +/*! + \since 5.13 + + Enables or disables the new filter engine functionality + inside the help engine, according to the passed \a uses parameter. + + \sa filterEngine() +*/ +void QHelpEngineCore::setUsesFilterEngine(bool uses) +{ + d->usesFilterEngine = uses; +} + +/*! + \since 5.13 + + Returns whether the help engine uses the new filter functionality. + + \sa filterEngine() +*/ +bool QHelpEngineCore::usesFilterEngine() const +{ + return d->usesFilterEngine; +} + QT_END_NAMESPACE diff --git a/src/assistant/help/qhelpenginecore.h b/src/assistant/help/qhelpenginecore.h index 6a2bf0e7d..91950290e 100644 --- a/src/assistant/help/qhelpenginecore.h +++ b/src/assistant/help/qhelpenginecore.h @@ -49,8 +49,8 @@ QT_BEGIN_NAMESPACE - class QHelpEngineCorePrivate; +class QHelpFilterEngine; class QHELP_EXPORT QHelpEngineCore : public QObject { @@ -63,6 +63,8 @@ public: explicit QHelpEngineCore(const QString &collectionFile, QObject *parent = nullptr); virtual ~QHelpEngineCore(); + QHelpFilterEngine *filterEngine() const; + bool setupData(); QString collectionFile() const; @@ -74,7 +76,10 @@ public: bool registerDocumentation(const QString &documentationFileName); bool unregisterDocumentation(const QString &namespaceName); QString documentationFileName(const QString &namespaceName); + QStringList registeredDocumentations() const; + QByteArray fileData(const QUrl &url) const; +#if QT_DEPRECATED_SINCE(5,13) QStringList customFilters() const; bool removeCustomFilter(const QString &filterName); bool addCustomFilter(const QString &filterName, @@ -86,13 +91,16 @@ public: QString currentFilter() const; void setCurrentFilter(const QString &filterName); - QStringList registeredDocumentations() const; QList<QStringList> filterAttributeSets(const QString &namespaceName) const; QList<QUrl> files(const QString namespaceName, const QStringList &filterAttributes, const QString &extensionFilter = QString()); +#endif + + QList<QUrl> files(const QString namespaceName, + const QString &filterName, + const QString &extensionFilter = QString()); QUrl findFile(const QUrl &url) const; - QByteArray fileData(const QUrl &url) const; QMap<QString, QUrl> linksForIdentifier(const QString &id) const; QMap<QString, QUrl> linksForKeyword(const QString &keyword) const; @@ -110,12 +118,18 @@ public: void setAutoSaveFilter(bool save); bool autoSaveFilter() const; + void setUsesFilterEngine(bool uses); + bool usesFilterEngine() const; + Q_SIGNALS: void setupStarted(); void setupFinished(); - void currentFilterChanged(const QString &newFilter); void warning(const QString &msg); + +#if QT_DEPRECATED_SINCE(5,13) + void currentFilterChanged(const QString &newFilter); void readersAboutToBeInvalidated(); +#endif protected: QHelpEngineCore(QHelpEngineCorePrivate *helpEngineCorePrivate, diff --git a/src/assistant/help/qhelpfilterdata.cpp b/src/assistant/help/qhelpfilterdata.cpp new file mode 100644 index 000000000..37b209541 --- /dev/null +++ b/src/assistant/help/qhelpfilterdata.cpp @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "qhelpfilterdata.h" +#include <QtCore/QVersionNumber> + +QT_BEGIN_NAMESPACE + +class QHelpFilterDataPrivate : public QSharedData +{ +public: + QHelpFilterDataPrivate() = default; + QHelpFilterDataPrivate(const QHelpFilterDataPrivate &other) + : QSharedData(other) + , m_components(other.m_components) + , m_versions(other.m_versions) + { } + ~QHelpFilterDataPrivate() = default; + + QStringList m_components; + QList<QVersionNumber> m_versions; +}; + +/*! + \class QHelpFilterData + \since 5.13 + \inmodule QtHelp + \brief The QHelpFilterData class provides details for the filters + used by QHelpFilterEngine. + + By using setComponents() you may constrain the search results to + documents that belong only to components specified on the given list. + By using setVersions() you may constrain the search results to + documents that belong only to versions specified on the given list. + + \sa QHelpFilterEngine +*/ + +/*! + Constructs the empty filter. +*/ +QHelpFilterData::QHelpFilterData() + : d(new QHelpFilterDataPrivate) +{ +} + +/*! + Constructs a copy of \a other. +*/ +QHelpFilterData::QHelpFilterData(const QHelpFilterData &) = default; + +/*! + Move-constructs a QHelpFilterData instance, making it point at the same object that \a other was pointing to. +*/ +QHelpFilterData::QHelpFilterData(QHelpFilterData &&) = default; + +/*! + Destroys the filter. +*/ +QHelpFilterData::~QHelpFilterData() = default; + +/*! + Assigns \a other to this filter and returns a reference to this filter. +*/ +QHelpFilterData &QHelpFilterData::operator=(const QHelpFilterData &) = default; + + +/*! + Move-assigns \a other to this QHelpFilterData instance. +*/ +QHelpFilterData &QHelpFilterData::operator=(QHelpFilterData &&) = default; + +/*! + \fn void QHelpFilterData::swap(QCompressedHelpInfo &other) + + Swaps the filter \a other with this filter. This + operation is very fast and never fails. +*/ + +bool QHelpFilterData::operator==(const QHelpFilterData &other) const +{ + return (d->m_components == other.d->m_components && + d->m_versions == other.d->m_versions); +} + +/*! + Specifies the component list that is used for filtering + the search results. Only results from components in the list + \a components shall be returned. +*/ +void QHelpFilterData::setComponents(const QStringList &components) +{ + d->m_components = components; +} + +/*! + Specifies the version list that is used for filtering + the search results. Only results from versions in the list + \a versions shall be returned. +*/ +void QHelpFilterData::setVersions(const QList<QVersionNumber> &versions) +{ + d->m_versions = versions; +} + +/*! + Returns the component list that is used for filtering + the search results. +*/ +QStringList QHelpFilterData::components() const +{ + return d->m_components; +} + +/*! + Returns the version list that is used for filtering + the search results. +*/ +QList<QVersionNumber> QHelpFilterData::versions() const +{ + return d->m_versions; +} + +QT_END_NAMESPACE diff --git a/src/assistant/help/qhelpfilterdata.h b/src/assistant/help/qhelpfilterdata.h new file mode 100644 index 000000000..b51ee495b --- /dev/null +++ b/src/assistant/help/qhelpfilterdata.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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$ +** +****************************************************************************/ + +#ifndef QHELPFILTERDATA_H +#define QHELPFILTERDATA_H + +#include <QtHelp/qhelp_global.h> + +#include <QtCore/QSharedDataPointer> + +QT_BEGIN_NAMESPACE + +class QVersionNumber; +class QHelpFilterDataPrivate; + +class QHELP_EXPORT QHelpFilterData final +{ +public: + QHelpFilterData(); + QHelpFilterData(const QHelpFilterData &other); + QHelpFilterData(QHelpFilterData &&other); + ~QHelpFilterData(); + + QHelpFilterData &operator=(const QHelpFilterData &other); + QHelpFilterData &operator=(QHelpFilterData &&other); + bool operator==(const QHelpFilterData &other) const; + + void swap(QHelpFilterData &other) Q_DECL_NOTHROW + { d.swap(other.d); } + + void setComponents(const QStringList &components); + void setVersions(const QList<QVersionNumber> &versions); + + QStringList components() const; + QList<QVersionNumber> versions() const; +private: + QSharedDataPointer<QHelpFilterDataPrivate> d; +}; + +QT_END_NAMESPACE + +#endif // QHELPFILTERDATA_H diff --git a/src/assistant/help/qhelpfilterengine.cpp b/src/assistant/help/qhelpfilterengine.cpp new file mode 100644 index 000000000..a53be506e --- /dev/null +++ b/src/assistant/help/qhelpfilterengine.cpp @@ -0,0 +1,290 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "qhelpfilterengine.h" +#include "qhelpenginecore.h" +#include "qhelpfilterdata.h" +#include "qhelpdbreader_p.h" +#include "qhelpcollectionhandler_p.h" + +#include <QtCore/QThread> +#include <QtCore/QVersionNumber> + +QT_BEGIN_NAMESPACE + +static const char ActiveFilter[] = "activeFilter"; + +class QHelpFilterEnginePrivate +{ +public: + bool setup(); + + QHelpFilterEngine *q = nullptr; + QHelpEngineCore *m_helpEngine = nullptr; + QHelpCollectionHandler *m_collectionHandler = nullptr; + QString m_currentFilter; + bool m_needsSetup = true; +}; + +bool QHelpFilterEnginePrivate::setup() +{ + if (!m_collectionHandler) + return false; + + if (!m_needsSetup) + return true; + + if (!m_helpEngine->setupData()) + return false; + + m_needsSetup = false; + + const QString filter = m_collectionHandler->customValue( + QLatin1String(ActiveFilter), QString()).toString(); + if (!filter.isEmpty() && m_collectionHandler->filters().contains(filter)) + m_currentFilter = filter; + + emit q->filterActivated(m_currentFilter); + return true; +} + +////////////// + +/*! + \class QHelpFilterEngine + \since 5.13 + \inmodule QtHelp + \brief The QHelpFilterEngine class provides a filtered view of the + help contents. + + The filter engine allows the management of filters associated with + a QHelpEngineCore instance. The help engine internally creates an + instance of the filter engine, which can be accessed by calling + QHelpEngineCore::filterEngine(). Therefore, the public constructor + of this class is disabled. + + The filters are identified by a filter name string. Filter details are + described by the \l QHelpFilterData class. + + The filter engine allows for adding new filters and changing the existing + filters' data through the setFilterData() method. An existing filter can + be removed through the removeFilter() method. + + Out of the registered filters one can be marked as the active one. + The active filter will be used by the associated help engine for returning + filtered results of many different functions, such as content, index, or + search results. If no filter is marked active, the help engine returns the + full results list available. + + The active filter is returned by activeFilter() and it can be changed by + setActiveFilter(). + + \sa QHelpEngineCore +*/ + +/*! + \fn void QHelpFilterEngine::filterActivated(const QString &newFilter) + + This signal is emitted when the active filter is set. \a newFilter + specifies the name of the filter. + + \sa setActiveFilter() +*/ + +/*! + \internal + Constructs the filter engine for \a helpEngine. +*/ +QHelpFilterEngine::QHelpFilterEngine(QHelpEngineCore *helpEngine) + : QObject(helpEngine), + d(new QHelpFilterEnginePrivate) +{ + d->q = this; + d->m_helpEngine = helpEngine; +} + +/*! + \internal + Destroys the existing filter engine. +*/ +QHelpFilterEngine::~QHelpFilterEngine() +{ + delete d; +} + +/*! + \internal + Sets the \a collectionHandler to be used for this filter engine. +*/ +void QHelpFilterEngine::setCollectionHandler(QHelpCollectionHandler *collectionHandler) +{ + d->m_collectionHandler = collectionHandler; + d->m_currentFilter = QString(); + d->m_needsSetup = true; +} + +/*! + Returns the map of all the available namespaces as keys + together with their associated components as values. +*/ +QMap<QString, QString> QHelpFilterEngine::namespaceToComponent() const +{ + if (!d->setup()) + return QMap<QString, QString>(); + return d->m_collectionHandler->namespaceToComponent(); +} + +/*! + Returns the map of all the available namespaces as keys + together with their associated versions as values. +*/ +QMap<QString, QVersionNumber> QHelpFilterEngine::namespaceToVersion() const +{ + if (!d->setup()) + return QMap<QString, QVersionNumber>(); + + return d->m_collectionHandler->namespaceToVersion(); +} + +/*! + Returns the list of all filter names defined inside the filter engine. +*/ +QStringList QHelpFilterEngine::filters() const +{ + if (!d->setup()) + return QStringList(); + return d->m_collectionHandler->filters(); +} + +/*! + Returns the list of all available components defined in all + registered documentation files. +*/ +QStringList QHelpFilterEngine::availableComponents() const +{ + if (!d->setup()) + return QStringList(); + return d->m_collectionHandler->availableComponents(); +} + +/*! + Returns the filter details associated with \a filterName. +*/ +QHelpFilterData QHelpFilterEngine::filterData(const QString &filterName) const +{ + if (!d->setup()) + return QHelpFilterData(); + return d->m_collectionHandler->filterData(filterName); +} + +/*! + Changes the existing filter details of the filter identified by + \a filterName to \a filterData. If the filter does not exist, a + new filter is created. + + Returns \c true if setting the filter succeeded, otherwise returns \c false. +*/ +bool QHelpFilterEngine::setFilterData(const QString &filterName, const QHelpFilterData &filterData) +{ + if (!d->setup()) + return false; + return d->m_collectionHandler->setFilterData(filterName, filterData); +} + +/*! + Removes the filter identified by \a filterName. + + Returns \c true if removing the filter succeeded, otherwise returns + \c false. +*/ +bool QHelpFilterEngine::removeFilter(const QString &filterName) +{ + if (!d->setup()) + return false; + return d->m_collectionHandler->removeFilter(filterName); +} + +/*! + Returns the name of the currently active filter. +*/ +QString QHelpFilterEngine::activeFilter() const +{ + if (!d->setup()) + return QString(); + return d->m_currentFilter; +} + +/*! + Changes the currently active filter to \a filterName. + + Returns \c true if changing the filter succeeded, otherwise + returns \c false. +*/ +bool QHelpFilterEngine::setActiveFilter(const QString &filterName) +{ + if (!d->setup()) + return false; + + if (filterName == d->m_currentFilter) + return true; + + if (!filterName.isEmpty() && !d->m_collectionHandler->filters().contains(filterName)) + return false; + + d->m_currentFilter = filterName; + d->m_collectionHandler->setCustomValue(QLatin1String(ActiveFilter), + d->m_currentFilter); + + emit filterActivated(d->m_currentFilter); + + return true; +} + +/*! + Returns the list of all registered documentation namespaces that match + the filter identified by \a filterName. +*/ +QStringList QHelpFilterEngine::namespacesForFilter(const QString &filterName) const +{ + if (!d->setup()) + return QStringList(); + return d->m_collectionHandler->namespacesForFilter(filterName); +} + +QT_END_NAMESPACE diff --git a/src/assistant/help/qhelpfilterengine.h b/src/assistant/help/qhelpfilterengine.h new file mode 100644 index 000000000..c4bd139f2 --- /dev/null +++ b/src/assistant/help/qhelpfilterengine.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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$ +** +****************************************************************************/ + +#ifndef QHELPFILTERENGINE_H +#define QHELPFILTERENGINE_H + +#include <QtHelp/qhelp_global.h> + +#include <QtCore/QObject> + +QT_BEGIN_NAMESPACE + +template <class K, class T> +class QMap; +class QVersionNumber; + +class QHelpCollectionHandler; +class QHelpEngineCore; +class QHelpFilterData; +class QHelpFilterEnginePrivate; + +class QHELP_EXPORT QHelpFilterEngine : public QObject +{ + Q_OBJECT +public: + QMap<QString, QString> namespaceToComponent() const; + QMap<QString, QVersionNumber> namespaceToVersion() const; + + QStringList filters() const; + + QString activeFilter() const; + bool setActiveFilter(const QString &filterName); + + QStringList availableComponents() const; + + QHelpFilterData filterData(const QString &filterName) const; + bool setFilterData(const QString &filterName, const QHelpFilterData &filterData); + + bool removeFilter(const QString &filterName); + + QStringList namespacesForFilter(const QString &filterName) const; + +Q_SIGNALS: + void filterActivated(const QString &newFilter); + +protected: + explicit QHelpFilterEngine(QHelpEngineCore *helpEngine); + virtual ~QHelpFilterEngine(); + +private: + void setCollectionHandler(QHelpCollectionHandler *collectionHandler); + + QHelpFilterEnginePrivate *d; + friend class QHelpEngineCore; + friend class QHelpEngineCorePrivate; +}; + +QT_END_NAMESPACE + +#endif // QHELPFILTERENGINE_H diff --git a/src/assistant/help/qhelpindexwidget.cpp b/src/assistant/help/qhelpindexwidget.cpp index 10b2863e6..fa70dd438 100644 --- a/src/assistant/help/qhelpindexwidget.cpp +++ b/src/assistant/help/qhelpindexwidget.cpp @@ -65,10 +65,10 @@ private: void run() override; QHelpEnginePrivate *m_helpEngine; - QStringList m_indices; + QString m_currentFilter; QStringList m_filterAttributes; + QStringList m_indices; mutable QMutex m_mutex; - bool m_abort = false; }; class QHelpIndexModelPrivate @@ -99,25 +99,20 @@ QHelpIndexProvider::~QHelpIndexProvider() void QHelpIndexProvider::collectIndices(const QString &customFilterName) { m_mutex.lock(); + m_currentFilter = customFilterName; m_filterAttributes = m_helpEngine->q->filterAttributes(customFilterName); m_mutex.unlock(); - if (!isRunning()) { - start(LowPriority); - } else { + + if (isRunning()) stopCollecting(); - start(LowPriority); - } + start(LowPriority); } void QHelpIndexProvider::stopCollecting() { if (!isRunning()) return; - m_mutex.lock(); - m_abort = true; - m_mutex.unlock(); wait(); - m_abort = false; } QStringList QHelpIndexProvider::indices() const @@ -129,9 +124,10 @@ QStringList QHelpIndexProvider::indices() const void QHelpIndexProvider::run() { m_mutex.lock(); - m_indices.clear(); + const QString currentFilter = m_currentFilter; const QStringList attributes = m_filterAttributes; const QString collectionFile = m_helpEngine->collectionHandler->collectionFile(); + m_indices = QStringList(); m_mutex.unlock(); if (collectionFile.isEmpty()) @@ -141,7 +137,9 @@ void QHelpIndexProvider::run() if (!collectionHandler.openCollectionFile()) return; - const QStringList result = collectionHandler.indicesForFilter(attributes); + const QStringList result = m_helpEngine->usesFilterEngine + ? collectionHandler.indicesForFilter(currentFilter) + : collectionHandler.indicesForFilter(attributes); m_mutex.lock(); m_indices = result; @@ -188,23 +186,27 @@ QHelpIndexModel::~QHelpIndexModel() delete d; } -void QHelpIndexModel::invalidateIndex(bool onShutDown) -{ - Q_UNUSED(onShutDown) -} - /*! Creates a new index by querying the help system for keywords for the specified \a customFilterName. */ void QHelpIndexModel::createIndex(const QString &customFilterName) { + const bool running = d->indexProvider->isRunning(); d->indexProvider->collectIndices(customFilterName); + if (running) + return; + + d->indices = QStringList(); + filter(QString()); emit indexCreationStarted(); } void QHelpIndexModel::insertIndices() { + if (d->indexProvider->isRunning()) + return; + d->indices = d->indexProvider->indices(); filter(QString()); emit indexCreated(); diff --git a/src/assistant/help/qhelpindexwidget.h b/src/assistant/help/qhelpindexwidget.h index 46d4937b7..58dda5e39 100644 --- a/src/assistant/help/qhelpindexwidget.h +++ b/src/assistant/help/qhelpindexwidget.h @@ -70,7 +70,6 @@ Q_SIGNALS: private Q_SLOTS: void insertIndices(); - void invalidateIndex(bool onShutDown = false); private: QHelpIndexModel(QHelpEnginePrivate *helpEngine); diff --git a/src/assistant/help/qhelpsearchengine.cpp b/src/assistant/help/qhelpsearchengine.cpp index 3ee9904e2..af7247cc3 100644 --- a/src/assistant/help/qhelpsearchengine.cpp +++ b/src/assistant/help/qhelpsearchengine.cpp @@ -231,7 +231,8 @@ private: m_searchInput = searchInput; indexReader->cancelSearching(); - indexReader->search(helpEngine->collectionFile(), indexFilesFolder(), searchInput); + indexReader->search(helpEngine->collectionFile(), indexFilesFolder(), + searchInput, helpEngine->usesFilterEngine()); } void cancelSearching() diff --git a/src/assistant/help/qhelpsearchindexreader.cpp b/src/assistant/help/qhelpsearchindexreader.cpp index 71be91f9f..8aaa5cc47 100644 --- a/src/assistant/help/qhelpsearchindexreader.cpp +++ b/src/assistant/help/qhelpsearchindexreader.cpp @@ -56,7 +56,7 @@ void QHelpSearchIndexReader::cancelSearching() } void QHelpSearchIndexReader::search(const QString &collectionFile, const QString &indexFilesFolder, - const QString &searchInput) + const QString &searchInput, bool usesFilterEngine) { wait(); @@ -65,6 +65,7 @@ void QHelpSearchIndexReader::search(const QString &collectionFile, const QString m_searchInput = searchInput; m_collectionFile = collectionFile; m_indexFilesFolder = indexFilesFolder; + m_usesFilterEngine = usesFilterEngine; start(QThread::NormalPriority); } diff --git a/src/assistant/help/qhelpsearchindexreader_default.cpp b/src/assistant/help/qhelpsearchindexreader_default.cpp index 25f9a13f2..06a8a8b19 100644 --- a/src/assistant/help/qhelpsearchindexreader_default.cpp +++ b/src/assistant/help/qhelpsearchindexreader_default.cpp @@ -38,6 +38,7 @@ ****************************************************************************/ #include "qhelpenginecore.h" +#include "qhelpfilterengine.h" #include "qhelpsearchindexreader_default_p.h" #include <QtCore/QSet> @@ -52,12 +53,20 @@ namespace qt { void Reader::setIndexPath(const QString &path) { m_indexPath = path; - m_namespaces.clear(); + m_namespaceAttributes.clear(); + m_filterEngineNamespaceList.clear(); + m_useFilterEngine = false; } -void Reader::addNamespace(const QString &namespaceName, const QStringList &attributes) +void Reader::addNamespaceAttributes(const QString &namespaceName, const QStringList &attributes) { - m_namespaces.insert(namespaceName, attributes); + m_namespaceAttributes.insert(namespaceName, attributes); +} + +void Reader::setFilterEngineNamespaceList(const QStringList &namespaceList) +{ + m_useFilterEngine = true; + m_filterEngineNamespaceList = namespaceList; } static QString namespacePlaceholders(const QMultiMap<QString, QStringList> &namespaces) @@ -106,18 +115,42 @@ static void bindNamespacesAndAttributes(QSqlQuery *query, const QMultiMap<QStrin } } +static QString namespacePlaceholders(const QStringList &namespaceList) +{ + QString placeholders; + bool firstNS = true; + for (int i = namespaceList.count(); i; --i) { + if (firstNS) + firstNS = false; + else + placeholders += QLatin1String(" OR "); + placeholders += QLatin1String("namespace = ?"); + } + return placeholders; +} + +static void bindNamespacesAndAttributes(QSqlQuery *query, const QStringList &namespaceList) +{ + for (const QString &ns : namespaceList) + query->addBindValue(ns); +} + QVector<QHelpSearchResult> Reader::queryTable(const QSqlDatabase &db, const QString &tableName, const QString &searchInput) const { - const QString &nsPlaceholders = namespacePlaceholders(m_namespaces); + const QString nsPlaceholders = m_useFilterEngine + ? namespacePlaceholders(m_filterEngineNamespaceList) + : namespacePlaceholders(m_namespaceAttributes); QSqlQuery query(db); query.prepare(QLatin1String("SELECT url, title, snippet(") + tableName + QLatin1String(", -1, '<b>', '</b>', '...', '10') FROM ") + tableName + QLatin1String(" WHERE (") + nsPlaceholders + QLatin1String(") AND ") + tableName + QLatin1String(" MATCH ? ORDER BY rank")); - bindNamespacesAndAttributes(&query, m_namespaces); + m_useFilterEngine + ? bindNamespacesAndAttributes(&query, m_filterEngineNamespaceList) + : bindNamespacesAndAttributes(&query, m_namespaceAttributes); query.addBindValue(searchInput); query.exec(); @@ -140,6 +173,7 @@ void Reader::searchInDB(const QString &searchInput) QSqlDatabase db = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), uniqueId); db.setConnectOptions(QLatin1String("QSQLITE_OPEN_READONLY")); db.setDatabaseName(m_indexPath + QLatin1String("/fts")); + if (db.open()) { const QVector<QHelpSearchResult> titleResults = queryTable(db, QLatin1String("titles"), searchInput); @@ -197,36 +231,39 @@ void QHelpSearchIndexReaderDefault::run() const QString searchInput = m_searchInput; const QString collectionFile = m_collectionFile; const QString indexPath = m_indexFilesFolder; + const bool usesFilterEngine = m_usesFilterEngine; lock.unlock(); - if (searchInput.isEmpty()) - return; - QHelpEngineCore engine(collectionFile, nullptr); if (!engine.setupData()) return; - const QStringList ®isteredDocs = engine.registeredDocumentations(); - emit searchingStarted(); - const QStringList ¤tFilter = engine.filterAttributes(engine.currentFilter()); - // setup the reader m_reader.setIndexPath(indexPath); - for (const QString &namespaceName : registeredDocs) { - const QList<QStringList> &attributeSets = - engine.filterAttributeSets(namespaceName); - for (const QStringList &attributes : attributeSets) { - if (attributesMatchFilter(attributes, currentFilter)) { - m_reader.addNamespace(namespaceName, attributes); + if (usesFilterEngine) { + m_reader.setFilterEngineNamespaceList( + engine.filterEngine()->namespacesForFilter( + engine.filterEngine()->activeFilter())); + } else { + const QStringList ®isteredDocs = engine.registeredDocumentations(); + const QStringList ¤tFilter = engine.filterAttributes(engine.currentFilter()); + + for (const QString &namespaceName : registeredDocs) { + const QList<QStringList> &attributeSets = + engine.filterAttributeSets(namespaceName); + + for (const QStringList &attributes : attributeSets) { + if (attributesMatchFilter(attributes, currentFilter)) { + m_reader.addNamespaceAttributes(namespaceName, attributes); + } } } } - lock.relock(); if (m_cancel) { emit searchingFinished(0); // TODO: check this, speed issue while locking??? diff --git a/src/assistant/help/qhelpsearchindexreader_default_p.h b/src/assistant/help/qhelpsearchindexreader_default_p.h index b31b532c6..c183a2357 100644 --- a/src/assistant/help/qhelpsearchindexreader_default_p.h +++ b/src/assistant/help/qhelpsearchindexreader_default_p.h @@ -64,7 +64,8 @@ class Reader { public: void setIndexPath(const QString &path); - void addNamespace(const QString &namespaceName, const QStringList &attributes); + void addNamespaceAttributes(const QString &namespaceName, const QStringList &attributes); + void setFilterEngineNamespaceList(const QStringList &namespaceList); void searchInDB(const QString &term); QVector<QHelpSearchResult> searchResults() const; @@ -74,9 +75,11 @@ private: const QString &tableName, const QString &searchInput) const; - QString m_indexPath; - QMultiMap<QString, QStringList> m_namespaces; + QMultiMap<QString, QStringList> m_namespaceAttributes; + QStringList m_filterEngineNamespaceList; QVector<QHelpSearchResult> m_searchResults; + QString m_indexPath; + bool m_useFilterEngine = false; }; diff --git a/src/assistant/help/qhelpsearchindexreader_p.h b/src/assistant/help/qhelpsearchindexreader_p.h index f6f3ee305..57295693b 100644 --- a/src/assistant/help/qhelpsearchindexreader_p.h +++ b/src/assistant/help/qhelpsearchindexreader_p.h @@ -74,7 +74,8 @@ public: void cancelSearching(); void search(const QString &collectionFile, const QString &indexFilesFolder, - const QString &searchInput); + const QString &searchInput, + bool usesFilterEngine = false); int searchResultCount() const; QVector<QHelpSearchResult> searchResults(int start, int end) const; @@ -89,6 +90,7 @@ protected: QString m_collectionFile; QString m_searchInput; QString m_indexFilesFolder; + bool m_usesFilterEngine = false; private: void run() override = 0; diff --git a/src/assistant/help/qhelpsearchquerywidget.cpp b/src/assistant/help/qhelpsearchquerywidget.cpp index de7f9e9f8..4cbd4b069 100644 --- a/src/assistant/help/qhelpsearchquerywidget.cpp +++ b/src/assistant/help/qhelpsearchquerywidget.cpp @@ -203,9 +203,6 @@ private: bool m_compactMode = false; }; -#include "qhelpsearchquerywidget.moc" - - /*! \class QHelpSearchQueryWidget \since 4.4 @@ -232,7 +229,7 @@ QHelpSearchQueryWidget::QHelpSearchQueryWidget(QWidget *parent) d = new QHelpSearchQueryWidgetPrivate(); QVBoxLayout *vLayout = new QVBoxLayout(this); - vLayout->setMargin(0); + vLayout->setContentsMargins(QMargins()); QHBoxLayout* hBoxLayout = new QHBoxLayout(); d->m_searchLabel = new QLabel(this); @@ -388,3 +385,5 @@ void QHelpSearchQueryWidget::changeEvent(QEvent *event) } QT_END_NAMESPACE + +#include "qhelpsearchquerywidget.moc" diff --git a/src/assistant/help/qhelpsearchresultwidget.cpp b/src/assistant/help/qhelpsearchresultwidget.cpp index 22b0bb4b2..b7d61b494 100644 --- a/src/assistant/help/qhelpsearchresultwidget.cpp +++ b/src/assistant/help/qhelpsearchresultwidget.cpp @@ -234,9 +234,6 @@ private: bool isIndexing = false; }; -#include "qhelpsearchresultwidget.moc" - - /*! \class QHelpSearchResultWidget \since 4.4 @@ -257,12 +254,12 @@ QHelpSearchResultWidget::QHelpSearchResultWidget(QHelpSearchEngine *engine) , d(new QHelpSearchResultWidgetPrivate(engine)) { QVBoxLayout *vLayout = new QVBoxLayout(this); - vLayout->setMargin(0); + vLayout->setContentsMargins(QMargins()); vLayout->setSpacing(0); QHBoxLayout *hBoxLayout = new QHBoxLayout(); #ifndef Q_OS_MAC - hBoxLayout->setMargin(0); + hBoxLayout->setContentsMargins(QMargins()); hBoxLayout->setSpacing(0); #endif hBoxLayout->addWidget(d->firstResultPage = d->setupToolButton( @@ -334,3 +331,5 @@ QUrl QHelpSearchResultWidget::linkAt(const QPoint &point) } QT_END_NAMESPACE + +#include "qhelpsearchresultwidget.moc" diff --git a/src/assistant/qhelpgenerator/helpgenerator.cpp b/src/assistant/qhelpgenerator/helpgenerator.cpp index ab42bd24f..f5493a59b 100644 --- a/src/assistant/qhelpgenerator/helpgenerator.cpp +++ b/src/assistant/qhelpgenerator/helpgenerator.cpp @@ -46,6 +46,7 @@ #include <QtCore/QFileInfo> #include <QtCore/QDir> #include <QtCore/QDebug> +#include <QtCore/QRegExp> #include <QtCore/QSet> #include <QtCore/QVariant> #include <QtCore/QDateTime> diff --git a/src/assistant/qhelpgenerator/main.cpp b/src/assistant/qhelpgenerator/main.cpp index 74d2275a7..ec8079c47 100644 --- a/src/assistant/qhelpgenerator/main.cpp +++ b/src/assistant/qhelpgenerator/main.cpp @@ -36,6 +36,7 @@ #include <QtCore/QDir> #include <QtCore/QFileInfo> #include <QtCore/QLibraryInfo> +#include <QtCore/QRegExp> #include <QtCore/QTranslator> #include <QtGui/QGuiApplication> |