File indexing completed on 2024-04-28 11:20:49
0001 /* 0002 SPDX-License-Identifier: GPL-2.0-or-later 0003 SPDX-FileCopyrightText: 2020 Shubham <aryan100jangid@gmail.com> 0004 SPDX-FileCopyrightText: 2020-2021 Alexander Semke <alexander.semke@web.de> 0005 */ 0006 0007 #include "cantor_macros.h" 0008 #include "documentationpanelwidget.h" 0009 0010 #include <KLocalizedString> 0011 #include <KMessageBox> 0012 #include <KConfigGroup> 0013 #include <KSharedConfig> 0014 0015 #include <QAction> 0016 #include <QCompleter> 0017 #include <QComboBox> 0018 #include <QDebug> 0019 #include <QFrame> 0020 #include <QHBoxLayout> 0021 #include <QHelpContentWidget> 0022 #include <QHelpIndexWidget> 0023 #include <QIcon> 0024 #include <QLabel> 0025 #include <QLineEdit> 0026 #include <QModelIndex> 0027 #include <QPushButton> 0028 #include <QShortcut> 0029 #include <QStackedWidget> 0030 #include <QToolButton> 0031 #include <QVBoxLayout> 0032 #include <QWebEngineDownloadItem> 0033 #include <QWebEngineProfile> 0034 #include <QWebEngineUrlScheme> 0035 #include <QWebEngineView> 0036 0037 DocumentationPanelWidget::DocumentationPanelWidget(QWidget* parent) : QWidget(parent) 0038 { 0039 m_webEngineView = new QWebEngineView(this); 0040 m_webEngineView->page()->action(QWebEnginePage::ViewSource)->setVisible(false); 0041 m_webEngineView->page()->action(QWebEnginePage::OpenLinkInNewTab)->setVisible(false); 0042 m_webEngineView->page()->action(QWebEnginePage::OpenLinkInNewWindow)->setVisible(false); 0043 m_webEngineView->page()->action(QWebEnginePage::DownloadLinkToDisk)->setVisible(false); 0044 m_webEngineView->page()->action(QWebEnginePage::Reload)->setVisible(false); 0045 0046 ///////////////////////// 0047 // Top toolbar layout // 0048 /////////////////////// 0049 QPushButton* home = new QPushButton(this); 0050 home->setIcon(QIcon::fromTheme(QLatin1String("go-home"))); 0051 home->setToolTip(i18nc("@button go to contents page", "Go to the contents")); 0052 home->setEnabled(false); 0053 0054 m_documentationSelector = new QComboBox(this); 0055 0056 // real time searcher 0057 m_search = new QLineEdit(this); 0058 m_search->setPlaceholderText(i18nc("@info:placeholder", "Search through keywords...")); 0059 m_search->setClearButtonEnabled(true); 0060 0061 // Add a seperator 0062 QFrame* seperator = new QFrame(this); 0063 seperator->setFrameShape(QFrame::VLine); 0064 seperator->setFrameShadow(QFrame::Sunken); 0065 0066 QPushButton* findPage = new QPushButton(this); 0067 findPage->setEnabled(false); 0068 findPage->setIcon(QIcon::fromTheme(QLatin1String("edit-find"))); 0069 findPage->setToolTip(i18nc("@info:tooltip", "Find in text of current documentation page")); 0070 findPage->setShortcut(QKeySequence(/*Qt::CTRL + */Qt::Key_F3)); 0071 0072 QPushButton* resetZoom = new QPushButton(this); 0073 resetZoom->setEnabled(false); 0074 resetZoom->setIcon(QIcon::fromTheme(QLatin1String("zoom-fit-best"))); 0075 resetZoom->setToolTip(i18nc("@info:tooltip", "Reset zoom level to 100%")); 0076 0077 QHBoxLayout* layout = new QHBoxLayout(this); 0078 layout->addWidget(home); 0079 layout->addWidget(m_documentationSelector); 0080 layout->addWidget(m_search); 0081 layout->addWidget(seperator); 0082 layout->addWidget(findPage); 0083 layout->addWidget(resetZoom); 0084 0085 QWidget* toolBarContainer = new QWidget(this); 0086 toolBarContainer->setLayout(layout); 0087 0088 // Add zoom in, zoom out behaviour on SHIFT++ and SHIFT-- 0089 auto zoomIn = new QShortcut(QKeySequence(Qt::SHIFT + Qt::Key_Plus), this); 0090 zoomIn->setContext(Qt::WidgetWithChildrenShortcut); 0091 0092 connect(zoomIn, &QShortcut::activated, this, [=]{ 0093 m_webEngineView->setZoomFactor(m_webEngineView->zoomFactor() + 0.1); 0094 emit zoomFactorChanged(); 0095 }); 0096 0097 auto zoomOut = new QShortcut(QKeySequence(Qt::SHIFT + Qt::Key_Minus), this); 0098 zoomOut->setContext(Qt::WidgetWithChildrenShortcut); 0099 0100 connect(zoomOut, &QShortcut::activated, this, [=]{ 0101 m_webEngineView->setZoomFactor(m_webEngineView->zoomFactor() - 0.1); 0102 emit zoomFactorChanged(); 0103 }); 0104 0105 connect(this, &DocumentationPanelWidget::zoomFactorChanged, [=]{ 0106 if(m_webEngineView->zoomFactor() != 1.0) 0107 resetZoom->setEnabled(true); 0108 else 0109 resetZoom->setEnabled(false); 0110 }); 0111 0112 //stack widget containing the web view and the content widget (will be added later in updateBacked()) 0113 m_stackedWidget = new QStackedWidget(this); 0114 m_stackedWidget->addWidget(m_webEngineView); 0115 0116 ///////////////////////////////// 0117 // Find in Page widget layout // 0118 /////////////////////////////// 0119 0120 // Add the Find in Page widget at the bottom, add all the widgets into a layout so that we can hide it 0121 QToolButton* hideButton = new QToolButton(this); 0122 hideButton->setIcon(QIcon::fromTheme(QLatin1String("dialog-close"))); 0123 hideButton->setToolTip(i18nc("@info:tooltip", "Close")); 0124 0125 QLabel* label = new QLabel(this); 0126 label->setText(i18n("Find:")); 0127 0128 m_findText = new QLineEdit(this); 0129 m_findText->setClearButtonEnabled(true); 0130 0131 QToolButton* next = new QToolButton(this); 0132 next->setIcon(QIcon::fromTheme(QLatin1String("go-down-search"))); 0133 next->setToolTip(i18nc("@info:tooltip", "Jump to next match")); 0134 0135 QToolButton* previous = new QToolButton(this); 0136 previous->setIcon(QIcon::fromTheme(QLatin1String("go-up-search"))); 0137 previous->setToolTip(i18nc("@info:tooltip", "Jump to previous match")); 0138 0139 m_matchCase = new QToolButton(this); 0140 m_matchCase->setIcon(QIcon::fromTheme(QLatin1String("format-text-superscript"))); 0141 m_matchCase->setToolTip(i18nc("@info:tooltip", "Match case sensitively")); 0142 m_matchCase->setCheckable(true); 0143 0144 // Create a layout for find in text widgets 0145 QHBoxLayout* lout = new QHBoxLayout(this); 0146 lout->addWidget(hideButton); 0147 lout->addWidget(label); 0148 lout->addWidget(m_findText); 0149 lout->addWidget(next); 0150 lout->addWidget(previous); 0151 lout->addWidget(m_matchCase); 0152 0153 QWidget* findPageWidgetContainer = new QWidget(this); 0154 findPageWidgetContainer->setLayout(lout); 0155 findPageWidgetContainer->hide(); 0156 0157 // Add topmost toolbar, display area and find in page widget in a Vertical layout 0158 QVBoxLayout* vlayout = new QVBoxLayout(this); 0159 vlayout->addWidget(toolBarContainer); 0160 vlayout->addWidget(m_stackedWidget); 0161 vlayout->addWidget(findPageWidgetContainer); 0162 0163 connect(m_documentationSelector, QOverload<int>::of(&QComboBox::currentIndexChanged), [=] { 0164 updateDocumentation(); 0165 m_stackedWidget->setCurrentIndex(1); 0166 }); 0167 0168 connect(m_stackedWidget, &QStackedWidget::currentChanged, [=]{ 0169 //disable Home and Search in Page buttons when stackwidget shows contents widget, enable when shows web browser 0170 bool enabled = (m_stackedWidget->currentIndex() == 0); //0 = web view, 1 = content widget 0171 findPage->setEnabled(enabled); 0172 home->setEnabled(enabled); 0173 }); 0174 0175 connect(home, &QPushButton::clicked, [=]{ 0176 m_stackedWidget->setCurrentIndex(1); //navigate to the content widget 0177 findPageWidgetContainer->hide(); 0178 }); 0179 0180 connect(resetZoom, &QPushButton::clicked, [=]{ 0181 m_webEngineView->setZoomFactor(1.0); 0182 resetZoom->setEnabled(false); 0183 }); 0184 0185 connect(m_search, &QLineEdit::returnPressed, this, &DocumentationPanelWidget::returnPressed); 0186 0187 // connect statements for Find in Page text widget 0188 connect(findPage, &QPushButton::clicked, [=]{ 0189 findPageWidgetContainer->show(); 0190 m_findText->clear(); 0191 m_findText->setFocus(); 0192 }); 0193 0194 connect(hideButton, &QToolButton::clicked, this, [=]{ 0195 findPageWidgetContainer->hide(); 0196 m_webEngineView->findText(QString()); // this clears up the selected text 0197 }); 0198 0199 connect(m_findText, &QLineEdit::returnPressed, this, &DocumentationPanelWidget::searchForward); 0200 connect(m_findText, &QLineEdit::textEdited, this, &DocumentationPanelWidget::searchForward); // for highlighting found string in real time 0201 connect(next, &QToolButton::clicked, this, &DocumentationPanelWidget::searchForward); 0202 connect(previous, &QToolButton::clicked, this, &DocumentationPanelWidget::searchBackward); 0203 connect(m_matchCase, &QAbstractButton::toggled, this, &DocumentationPanelWidget::searchForward); 0204 connect(m_matchCase, &QAbstractButton::toggled, this, [=]{ 0205 m_webEngineView->findText(QString()); 0206 searchForward(); 0207 }); 0208 0209 // for webenginebrowser for downloading of images or html pages 0210 connect(m_webEngineView->page()->profile(), &QWebEngineProfile::downloadRequested, 0211 this, &DocumentationPanelWidget::downloadResource); 0212 } 0213 0214 DocumentationPanelWidget::~DocumentationPanelWidget() 0215 { 0216 delete m_indexWidget; 0217 delete m_contentWidget; 0218 delete m_engine; 0219 delete m_webEngineView; 0220 delete m_stackedWidget; 0221 delete m_search; 0222 delete m_findText; 0223 delete m_matchCase; 0224 delete m_documentationSelector; 0225 } 0226 0227 void DocumentationPanelWidget::updateBackend(const QString& newBackend) 0228 { 0229 qDebug()<<"update backend " << newBackend; 0230 //nothing to do if the same backend was provided 0231 if(m_backend == newBackend) 0232 return; 0233 0234 m_backend = newBackend; 0235 0236 m_initializing = true; 0237 0238 // show all available documentation files for the new backend 0239 m_documentationSelector->clear(); 0240 const KConfigGroup& group = KSharedConfig::openConfig(QStringLiteral("cantorrc"))->group(m_backend.toLower()); 0241 m_docNames = group.readEntry(QLatin1String("Names"), QStringList()); 0242 m_docPaths = group.readEntry(QLatin1String("Paths"), QStringList()); 0243 const QStringList& iconNames = group.readEntry(QLatin1String("Icons"), QStringList()); 0244 for (int i = 0; i < m_docNames.size(); ++i) { 0245 const QString& name = m_docNames.at(i); 0246 QString iconName; 0247 if (i < iconNames.size()) 0248 iconName = iconNames.at(i); 0249 0250 m_documentationSelector->addItem(QIcon::fromTheme(iconName), name); 0251 } 0252 0253 m_initializing = false; 0254 0255 //select the first available documentation file which will trigger the re-initialization of QHelpEngine 0256 //TODO: restore from the saved state the previously selected documentation in m_documentationSelector for the current backend 0257 if (!m_docNames.isEmpty()) 0258 m_documentationSelector->setCurrentIndex(0); 0259 0260 updateDocumentation(); 0261 0262 if (!m_docNames.isEmpty()) 0263 { 0264 m_webEngineView->show(); 0265 m_stackedWidget->setCurrentIndex(1); 0266 } 0267 else 0268 m_webEngineView->hide(); 0269 } 0270 0271 /*! 0272 * called if another documentation file was selected in the ComboBox for all available documentations 0273 * for the current backend. This slot triggers the re-initialization of QHelpEngine with the proper 0274 * documentation file and also updates the content of the widgets showing the documentation. 0275 */ 0276 void DocumentationPanelWidget::updateDocumentation() 0277 { 0278 if (m_initializing) 0279 return; 0280 0281 //remove the currently shown content widget, will be replaced with the new one after 0282 //the help engine was initialized with the new documentation file 0283 if(m_contentWidget) 0284 { 0285 m_stackedWidget->removeWidget(m_contentWidget); 0286 m_search->clear(); 0287 } 0288 0289 //unregister the previous help engine qch files 0290 if(!m_currentQchFileName.isEmpty()) 0291 { 0292 const QString& fileNamespace = QHelpEngineCore::namespaceName(m_currentQchFileName); 0293 if(m_engine->registeredDocumentations().contains(fileNamespace)) 0294 m_engine->unregisterDocumentation(m_currentQchFileName); 0295 } 0296 0297 if (m_docNames.isEmpty()) 0298 { 0299 m_contentWidget = nullptr; 0300 m_indexWidget = nullptr; 0301 return; 0302 } 0303 0304 //initialize the Qt Help engine and provide the proper help collection file for the current backend 0305 //and for the currently selected documentation for this backend 0306 int index = m_documentationSelector->currentIndex(); 0307 if (index < m_docPaths.size()) 0308 m_currentQchFileName = m_docPaths.at(index); 0309 0310 QString qhcFileName = m_currentQchFileName; 0311 qhcFileName.replace(QLatin1String("qch"), QLatin1String("qhc")); 0312 m_engine = new QHelpEngine(qhcFileName, this); 0313 /*if(!m_engine->setupData()) 0314 qWarning() << "Couldn't setup QtHelp Engine: " << m_engine->error();*/ 0315 0316 // if(m_backend != QLatin1String("octave")) 0317 m_engine->setProperty("_q_readonly", QVariant::fromValue<bool>(true)); 0318 0319 //index widget 0320 m_indexWidget = m_engine->indexWidget(); 0321 connect(m_indexWidget, &QHelpIndexWidget::linkActivated, this, &DocumentationPanelWidget::showUrl); 0322 0323 //content widget 0324 m_contentWidget = m_engine->contentWidget(); 0325 m_stackedWidget->addWidget(m_contentWidget); 0326 connect(m_contentWidget, &QHelpContentWidget::linkActivated, this, &DocumentationPanelWidget::showUrl); 0327 0328 //search widget 0329 auto* completer = new QCompleter(m_indexWidget->model(), m_search); 0330 m_search->setCompleter(completer); 0331 completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion); 0332 completer->setCaseSensitivity(Qt::CaseInsensitive); 0333 connect(completer, QOverload<const QModelIndex&>::of(&QCompleter::activated), this, &DocumentationPanelWidget::returnPressed); 0334 0335 // handle the URL scheme handler 0336 //m_webEngineView->page()->profile()->removeUrlScheme("qthelp"); 0337 m_webEngineView->page()->profile()->removeAllUrlSchemeHandlers(); // remove previously installed scheme handler and then install new one 0338 m_webEngineView->page()->profile()->installUrlSchemeHandler("qthelp", new QtHelpSchemeHandler(m_engine)); 0339 0340 // register the compressed help file (qch) 0341 const QString& nameSpace = QHelpEngineCore::namespaceName(m_currentQchFileName); 0342 if(!m_engine->registeredDocumentations().contains(nameSpace)) 0343 { 0344 if(m_engine->registerDocumentation(m_currentQchFileName)) 0345 qDebug()<<"The documentation file " << m_currentQchFileName << " successfully registered."; 0346 else 0347 qWarning() << m_engine->error(); 0348 } 0349 } 0350 0351 void DocumentationPanelWidget::showUrl(const QUrl& url) 0352 { 0353 m_webEngineView->load(url); 0354 m_stackedWidget->setCurrentIndex(0); //show the web engine view 0355 } 0356 0357 QUrl DocumentationPanelWidget::url() const 0358 { 0359 return m_webEngineView->url(); 0360 } 0361 0362 void DocumentationPanelWidget::returnPressed() 0363 { 0364 const QString& input = m_search->text(); 0365 0366 if (input.isEmpty()) 0367 return; 0368 0369 contextSensitiveHelp(input); 0370 } 0371 0372 void DocumentationPanelWidget::contextSensitiveHelp(const QString& keyword) 0373 { 0374 qDebug() << "requested the documentation for the keyword " << keyword; 0375 0376 //make sure first we show the web view in the stack widget 0377 m_stackedWidget->setCurrentIndex(0); 0378 0379 if (!m_indexWidget) 0380 return; 0381 0382 m_indexWidget->filterIndices(keyword); // filter exactly, no wildcards 0383 m_indexWidget->activateCurrentItem(); // this internally emitts the QHelpIndexWidget::linkActivated signal 0384 0385 // called in order to refresh and restore the index widget 0386 // otherwise filterIndices() filters the indices list, and then the index widget only contains the matched keywords 0387 m_indexWidget->filterIndices(QString()); 0388 } 0389 0390 void DocumentationPanelWidget::searchForward() 0391 { 0392 m_matchCase->isChecked() ? m_webEngineView->findText(m_findText->text(), QWebEnginePage::FindCaseSensitively) : 0393 m_webEngineView->findText(m_findText->text()); 0394 } 0395 0396 void DocumentationPanelWidget::searchBackward() 0397 { 0398 m_matchCase->isChecked() ? m_webEngineView->findText(m_findText->text(), QWebEnginePage::FindCaseSensitively | QWebEnginePage::FindBackward) : 0399 m_webEngineView->findText(m_findText->text(), QWebEnginePage::FindBackward); 0400 } 0401 0402 void DocumentationPanelWidget::downloadResource(QWebEngineDownloadItem* resource) 0403 { 0404 // default download directory is ~/Downloads on Linux 0405 m_webEngineView->page()->download(resource->url()); 0406 resource->accept(); 0407 0408 KMessageBox::information(this, i18n("The file has been downloaded successfully at Downloads."), i18n("Download Successful")); 0409 0410 disconnect(m_webEngineView->page()->profile(), &QWebEngineProfile::downloadRequested, this, &DocumentationPanelWidget::downloadResource); 0411 }