documentationpanelwidget.cpp 15.4 KB
Newer Older
1
/*
2
3
4
5
    SPDX-License-Identifier: GPL-2.0-or-later
    SPDX-FileCopyrightText: 2020 Shubham <aryan100jangid@gmail.com>
    SPDX-FileCopyrightText: 2020-2021 Alexander Semke <alexander.semke@web.de>
*/
6
7

#include "cantor_macros.h"
8
#include "documentationpanelwidget.h"
9
10

#include <KLocalizedString>
11
#include <KMessageBox>
12
13
#include <KConfigGroup>
#include <KSharedConfig>
14

15
#include <QAction>
Shubham  .'s avatar
Shubham . committed
16
#include <QCompleter>
17
#include <QComboBox>
Shubham  .'s avatar
Shubham . committed
18
#include <QDebug>
Shubham  .'s avatar
Shubham . committed
19
#include <QFrame>
Shubham  .'s avatar
Shubham . committed
20
#include <QHBoxLayout>
21
22
23
#include <QHelpContentWidget>
#include <QHelpIndexWidget>
#include <QIcon>
Shubham  .'s avatar
Shubham . committed
24
#include <QLabel>
25
#include <QLineEdit>
Shubham  .'s avatar
Shubham . committed
26
#include <QModelIndex>
27
#include <QPushButton>
28
#include <QShortcut>
29
#include <QStackedWidget>
Shubham  .'s avatar
Shubham . committed
30
#include <QToolButton>
Shubham  .'s avatar
Shubham . committed
31
#include <QVBoxLayout>
32
#include <QWebEngineDownloadItem>
Shubham  .'s avatar
Shubham . committed
33
34
#include <QWebEngineProfile>
#include <QWebEngineUrlScheme>
35
#include <QWebEngineView>
36

Shubham  .'s avatar
Shubham . committed
37
DocumentationPanelWidget::DocumentationPanelWidget(QWidget* parent) : QWidget(parent)
38
{
39
40
41
42
43
44
    m_webEngineView = new QWebEngineView(this);
    m_webEngineView->page()->action(QWebEnginePage::ViewSource)->setVisible(false);
    m_webEngineView->page()->action(QWebEnginePage::OpenLinkInNewTab)->setVisible(false);
    m_webEngineView->page()->action(QWebEnginePage::OpenLinkInNewWindow)->setVisible(false);
    m_webEngineView->page()->action(QWebEnginePage::DownloadLinkToDisk)->setVisible(false);
    m_webEngineView->page()->action(QWebEnginePage::Reload)->setVisible(false);
Shubham  .'s avatar
Shubham . committed
45

Shubham  .'s avatar
Shubham . committed
46
47
48
    /////////////////////////
    // Top toolbar layout //
    ///////////////////////
49
    QPushButton* home = new QPushButton(this);
Shubham  .'s avatar
Shubham . committed
50
51
    home->setIcon(QIcon::fromTheme(QLatin1String("go-home")));
    home->setToolTip(i18nc("@button go to contents page", "Go to the contents"));
52
    home->setEnabled(false);
53

54
    m_documentationSelector = new QComboBox(this);
55

Shubham  .'s avatar
Shubham . committed
56
57
58
59
60
    // real time searcher
    m_search = new QLineEdit(this);
    m_search->setPlaceholderText(i18nc("@info:placeholder", "Search through keywords..."));
    m_search->setClearButtonEnabled(true);

Shubham  .'s avatar
Shubham . committed
61
    // Add a seperator
62
    QFrame* seperator = new QFrame(this);
Shubham  .'s avatar
Shubham . committed
63
64
    seperator->setFrameShape(QFrame::VLine);
    seperator->setFrameShadow(QFrame::Sunken);
65

66
    QPushButton* findPage = new QPushButton(this);
67
    findPage->setEnabled(false);
Shubham  .'s avatar
Shubham . committed
68
69
    findPage->setIcon(QIcon::fromTheme(QLatin1String("edit-find")));
    findPage->setToolTip(i18nc("@info:tooltip", "Find in text of current documentation page"));
70
    findPage->setShortcut(QKeySequence(/*Qt::CTRL + */Qt::Key_F3));
71

72
73
74
75
76
    QPushButton* resetZoom = new QPushButton(this);
    resetZoom->setEnabled(false);
    resetZoom->setIcon(QIcon::fromTheme(QLatin1String("zoom-fit-best")));
    resetZoom->setToolTip(i18nc("@info:tooltip", "Reset zoom level to 100%"));

Shubham  .'s avatar
Shubham . committed
77
78
    QHBoxLayout* layout = new QHBoxLayout(this);
    layout->addWidget(home);
79
    layout->addWidget(m_documentationSelector);
Shubham  .'s avatar
Shubham . committed
80
81
82
83
    layout->addWidget(m_search);
    layout->addWidget(seperator);
    layout->addWidget(findPage);
    layout->addWidget(resetZoom);
84

Shubham  .'s avatar
Shubham . committed
85
86
    QWidget* toolBarContainer = new QWidget(this);
    toolBarContainer->setLayout(layout);
Shubham  .'s avatar
Shubham . committed
87

88
89
90
    // Add zoom in, zoom out behaviour on SHIFT++ and SHIFT--
    auto zoomIn = new QShortcut(QKeySequence(Qt::SHIFT + Qt::Key_Plus), this);
    zoomIn->setContext(Qt::WidgetWithChildrenShortcut);
Shubham  .'s avatar
Shubham . committed
91

92
    connect(zoomIn, &QShortcut::activated, this, [=]{
93
        m_webEngineView->setZoomFactor(m_webEngineView->zoomFactor() + 0.1);
Shubham  .'s avatar
Shubham . committed
94
        emit zoomFactorChanged();
95
96
97
98
    });

    auto zoomOut = new QShortcut(QKeySequence(Qt::SHIFT + Qt::Key_Minus), this);
    zoomOut->setContext(Qt::WidgetWithChildrenShortcut);
Shubham  .'s avatar
Shubham . committed
99

100
    connect(zoomOut, &QShortcut::activated, this, [=]{
101
        m_webEngineView->setZoomFactor(m_webEngineView->zoomFactor() - 0.1);
Shubham  .'s avatar
Shubham . committed
102
        emit zoomFactorChanged();
103
    });
Shubham  .'s avatar
Shubham . committed
104

Shubham  .'s avatar
Shubham . committed
105
    connect(this, &DocumentationPanelWidget::zoomFactorChanged, [=]{
106
        if(m_webEngineView->zoomFactor() != 1.0)
Shubham  .'s avatar
Shubham . committed
107
108
109
110
            resetZoom->setEnabled(true);
        else
            resetZoom->setEnabled(false);
    });
Shubham  .'s avatar
Shubham . committed
111

112
    //stack widget containing the web view and the content widget (will be added later in updateBacked())
113
    m_stackedWidget = new QStackedWidget(this);
114
    m_stackedWidget->addWidget(m_webEngineView);
Shubham  .'s avatar
Shubham . committed
115
116
117
118
119

    /////////////////////////////////
    // Find in Page widget layout //
    ///////////////////////////////

Shubham  .'s avatar
Shubham . committed
120
121
122
123
124
125
126
127
    // Add the Find in Page widget at the bottom, add all the widgets into a layout so that we can hide it
    QToolButton* hideButton = new QToolButton(this);
    hideButton->setIcon(QIcon::fromTheme(QLatin1String("dialog-close")));
    hideButton->setToolTip(i18nc("@info:tooltip", "Close"));

    QLabel* label = new QLabel(this);
    label->setText(i18n("Find:"));

128
129
    m_findText = new QLineEdit(this);
    m_findText->setClearButtonEnabled(true);
Shubham  .'s avatar
Shubham . committed
130
131

    QToolButton* next = new QToolButton(this);
132
    next->setIcon(QIcon::fromTheme(QLatin1String("go-down-search")));
Shubham  .'s avatar
Shubham . committed
133
134
135
    next->setToolTip(i18nc("@info:tooltip", "Jump to next match"));

    QToolButton* previous = new QToolButton(this);
136
    previous->setIcon(QIcon::fromTheme(QLatin1String("go-up-search")));
Shubham  .'s avatar
Shubham . committed
137
138
    previous->setToolTip(i18nc("@info:tooltip", "Jump to previous match"));

139
140
    m_matchCase = new QToolButton(this);
    m_matchCase->setIcon(QIcon::fromTheme(QLatin1String("format-text-superscript")));
Shubham  .'s avatar
Shubham . committed
141
    m_matchCase->setToolTip(i18nc("@info:tooltip", "Match case sensitively"));
142
    m_matchCase->setCheckable(true);
Shubham  .'s avatar
Shubham . committed
143

144
    // Create a layout for find in text widgets
Shubham  .'s avatar
Shubham . committed
145
146
147
    QHBoxLayout* lout = new QHBoxLayout(this);
    lout->addWidget(hideButton);
    lout->addWidget(label);
148
    lout->addWidget(m_findText);
Shubham  .'s avatar
Shubham . committed
149
150
    lout->addWidget(next);
    lout->addWidget(previous);
151
    lout->addWidget(m_matchCase);
Shubham  .'s avatar
Shubham . committed
152
153
154
155
156

    QWidget* findPageWidgetContainer = new QWidget(this);
    findPageWidgetContainer->setLayout(lout);
    findPageWidgetContainer->hide();

Shubham  .'s avatar
Shubham . committed
157
158
159
    // Add topmost toolbar, display area and find in page widget in a Vertical layout
    QVBoxLayout* vlayout = new QVBoxLayout(this);
    vlayout->addWidget(toolBarContainer);
160
    vlayout->addWidget(m_stackedWidget);
Shubham  .'s avatar
Shubham . committed
161
    vlayout->addWidget(findPageWidgetContainer);
162

163
164
165
166
    connect(m_documentationSelector, QOverload<int>::of(&QComboBox::currentIndexChanged), [=] {
        updateDocumentation();
        m_stackedWidget->setCurrentIndex(1);
    });
167

168
    connect(m_stackedWidget, &QStackedWidget::currentChanged, [=]{
169
        //disable Home and Search in Page buttons when stackwidget shows contents widget, enable when shows web browser
170
171
172
        bool enabled = (m_stackedWidget->currentIndex() == 0); //0 = web view, 1 = content widget
        findPage->setEnabled(enabled);
        home->setEnabled(enabled);
173
174
175
    });

    connect(home, &QPushButton::clicked, [=]{
176
        m_stackedWidget->setCurrentIndex(1); //navigate to the content widget
177
        findPageWidgetContainer->hide();
178
179
    });

180
    connect(resetZoom, &QPushButton::clicked, [=]{
181
        m_webEngineView->setZoomFactor(1.0);
Shubham  .'s avatar
Shubham . committed
182
        resetZoom->setEnabled(false);
183
184
    });

185
    connect(m_search, &QLineEdit::returnPressed, this, &DocumentationPanelWidget::returnPressed);
186

187
    // connect statements for Find in Page text widget
Shubham  .'s avatar
Shubham . committed
188
189
190
191
192
193
    connect(findPage, &QPushButton::clicked, [=]{
        findPageWidgetContainer->show();
        m_findText->clear();
        m_findText->setFocus();
    });

194
195
    connect(hideButton, &QToolButton::clicked, this, [=]{
        findPageWidgetContainer->hide();
196
        m_webEngineView->findText(QString()); // this clears up the selected text
197
198
    });

199
200
201
202
203
    connect(m_findText, &QLineEdit::returnPressed, this, &DocumentationPanelWidget::searchForward);
    connect(m_findText, &QLineEdit::textEdited, this, &DocumentationPanelWidget::searchForward); // for highlighting found string in real time
    connect(next, &QToolButton::clicked, this, &DocumentationPanelWidget::searchForward);
    connect(previous, &QToolButton::clicked, this, &DocumentationPanelWidget::searchBackward);
    connect(m_matchCase, &QAbstractButton::toggled, this, &DocumentationPanelWidget::searchForward);
204
    connect(m_matchCase, &QAbstractButton::toggled, this, [=]{
205
        m_webEngineView->findText(QString());
206
        searchForward();
207
    });
208
209

    // for webenginebrowser for downloading of images or html pages
210
211
    connect(m_webEngineView->page()->profile(), &QWebEngineProfile::downloadRequested,
            this, &DocumentationPanelWidget::downloadResource);
212
213
}

Shubham  .'s avatar
Shubham . committed
214
215
DocumentationPanelWidget::~DocumentationPanelWidget()
{
216
217
    delete m_indexWidget;
    delete m_contentWidget;
Shubham  .'s avatar
Shubham . committed
218
    delete m_engine;
219
220
    delete m_webEngineView;
    delete m_stackedWidget;
221
    delete m_search;
222
223
    delete m_findText;
    delete m_matchCase;
224
    delete m_documentationSelector;
Shubham  .'s avatar
Shubham . committed
225
226
}

227
void DocumentationPanelWidget::updateBackend(const QString& newBackend)
228
{
229
    qDebug()<<"update backend " << newBackend;
230
    //nothing to do if the same backend was provided
Shubham  .'s avatar
Shubham . committed
231
232
233
    if(m_backend == newBackend)
        return;

Shubham  .'s avatar
Shubham . committed
234
    m_backend = newBackend;
235
    m_initializing = true;
236

237
238
    // show all available documentation files for the new backend
    m_documentationSelector->clear();
239
    const KConfigGroup& group = KSharedConfig::openConfig()->group(m_backend.toLower());
240
241
    m_docNames = group.readEntry(QLatin1String("Names"), QStringList());
    m_docPaths = group.readEntry(QLatin1String("Paths"), QStringList());
242
243
244
245
246
247
248
249
250
    const QStringList& iconNames = group.readEntry(QLatin1String("Icons"), QStringList());
    for (int i = 0; i < m_docNames.size(); ++i) {
        const QString& name = m_docNames.at(i);
        QString iconName;
        if (i < iconNames.size())
            iconName = iconNames.at(i);

        m_documentationSelector->addItem(QIcon::fromTheme(iconName), name);
    }
251

252
253
254
255
    m_initializing = false;

    //select the first available documentation file which will trigger the re-initialization of QHelpEngine
    //TODO: restore from the saved state the previously selected documentation in m_documentationSelector for the current backend
256
    if (!m_docNames.isEmpty())
257
        m_documentationSelector->setCurrentIndex(0);
258

Alexander Semke's avatar
Alexander Semke committed
259
    updateDocumentation();
260
261
262
263
264
265
266
267

    if (!m_docNames.isEmpty())
    {
        m_webEngineView->show();
        m_stackedWidget->setCurrentIndex(1);
    }
    else
        m_webEngineView->hide();
268
269
270
271
272
273
274
275
276
277
278
279
}

/*!
 * called if another documentation file was selected in the ComboBox for all available documentations
 * for the current backend. This slot triggers the re-initialization of QHelpEngine with the proper
 * documentation file and also updates the content of the widgets showing the documentation.
 */
void DocumentationPanelWidget::updateDocumentation()
{
    if (m_initializing)
        return;

280
281
282
    //remove the currently shown content widget, will be replaced with the new one after
    //the help engine was initialized with the new documentation file
    if(m_contentWidget)
Shubham  .'s avatar
Shubham . committed
283
    {
284
        m_stackedWidget->removeWidget(m_contentWidget);
Shubham  .'s avatar
Shubham . committed
285
286
287
        m_search->clear();
    }

288
    //unregister the previous help engine qch files
289
    if(!m_currentQchFileName.isEmpty())
290
    {
291
        const QString& fileNamespace = QHelpEngineCore::namespaceName(m_currentQchFileName);
292
        if(m_engine->registeredDocumentations().contains(fileNamespace))
293
            m_engine->unregisterDocumentation(m_currentQchFileName);
294
    }
295

296
    if (m_docNames.isEmpty())
297
    {
298
299
        m_contentWidget = nullptr;
        m_indexWidget = nullptr;
300
301
302
        return;
    }

303
304
    //initialize the Qt Help engine and provide the proper help collection file for the current backend
    //and for the currently selected documentation for this backend
305
    int index = m_documentationSelector->currentIndex();
306
307
    if (index < m_docPaths.size())
        m_currentQchFileName = m_docPaths.at(index);
Alexander Semke's avatar
Alexander Semke committed
308

309
310
    QString qhcFileName = m_currentQchFileName;
    qhcFileName.replace(QLatin1String("qch"), QLatin1String("qhc"));
311
    m_engine = new QHelpEngine(qhcFileName, this);
312
313
    /*if(!m_engine->setupData())
         qWarning() << "Couldn't setup QtHelp Engine: " << m_engine->error();*/
Shubham  .'s avatar
Shubham . committed
314

315
//     if(m_backend != QLatin1String("octave"))
316
317
318
      m_engine->setProperty("_q_readonly", QVariant::fromValue<bool>(true));

    //index widget
319
    m_indexWidget = m_engine->indexWidget();
320
    connect(m_indexWidget, &QHelpIndexWidget::linkActivated, this, &DocumentationPanelWidget::showUrl);
321
322

    //content widget
323
    m_contentWidget = m_engine->contentWidget();
324
325
    m_stackedWidget->addWidget(m_contentWidget);
    connect(m_contentWidget, &QHelpContentWidget::linkActivated, this, &DocumentationPanelWidget::showUrl);
326
327

    //search widget
328
    auto* completer = new QCompleter(m_indexWidget->model(), m_search);
329
330
331
332
    m_search->setCompleter(completer);
    completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
    completer->setCaseSensitivity(Qt::CaseInsensitive);
    connect(completer, QOverload<const QModelIndex&>::of(&QCompleter::activated), this, &DocumentationPanelWidget::returnPressed);
333

334
    // handle the URL scheme handler
335
336
337
    //m_webEngineView->page()->profile()->removeUrlScheme("qthelp");
    m_webEngineView->page()->profile()->removeAllUrlSchemeHandlers(); // remove previously installed scheme handler and then install new one
    m_webEngineView->page()->profile()->installUrlSchemeHandler("qthelp", new QtHelpSchemeHandler(m_engine));
338

339
    // register the compressed help file (qch)
340
    const QString& nameSpace = QHelpEngineCore::namespaceName(m_currentQchFileName);
341
    if(!m_engine->registeredDocumentations().contains(nameSpace))
Shubham  .'s avatar
Shubham . committed
342
    {
343
344
        if(m_engine->registerDocumentation(m_currentQchFileName))
            qDebug()<<"The documentation file " << m_currentQchFileName << " successfully registered.";
345
        else
Shubham  .'s avatar
Shubham . committed
346
            qWarning() << m_engine->error();
347
    }
Shubham  .'s avatar
Shubham . committed
348
349
}

350
void DocumentationPanelWidget::showUrl(const QUrl& url)
351
{
352
    m_webEngineView->load(url);
353
354
355
    m_stackedWidget->setCurrentIndex(0); //show the web engine view
}

356
QUrl DocumentationPanelWidget::url() const
357
{
358
    return m_webEngineView->url();
359
360
361
362
363
364
}

void DocumentationPanelWidget::returnPressed()
{
    const QString& input = m_search->text();

Shubham  .'s avatar
Shubham . committed
365
366
    if (input.isEmpty())
        return;
367

368
    contextSensitiveHelp(input);
369
370
}

371
void DocumentationPanelWidget::contextSensitiveHelp(const QString& keyword)
372
{
373
    qDebug() << "requested the documentation for the keyword " << keyword;
Shubham  .'s avatar
Shubham . committed
374

375
376
    //make sure first we show the web view in the stack widget
    m_stackedWidget->setCurrentIndex(0);
377

378
379
    m_indexWidget->filterIndices(keyword); // filter exactly, no wildcards
    m_indexWidget->activateCurrentItem(); // this internally emitts the QHelpIndexWidget::linkActivated signal
Shubham  .'s avatar
Shubham . committed
380
381
382

    // called in order to refresh and restore the index widget
    // otherwise filterIndices() filters the indices list, and then the index widget only contains the matched keywords
383
    m_indexWidget->filterIndices(QString());
384
385
}

386
387
void DocumentationPanelWidget::searchForward()
{
388
389
    m_matchCase->isChecked() ? m_webEngineView->findText(m_findText->text(), QWebEnginePage::FindCaseSensitively) :
                               m_webEngineView->findText(m_findText->text());
390
391
392
393
}

void DocumentationPanelWidget::searchBackward()
{
394
395
    m_matchCase->isChecked() ? m_webEngineView->findText(m_findText->text(), QWebEnginePage::FindCaseSensitively | QWebEnginePage::FindBackward) :
                               m_webEngineView->findText(m_findText->text(), QWebEnginePage::FindBackward);
396
397
}

398
399
400
void DocumentationPanelWidget::downloadResource(QWebEngineDownloadItem* resource)
{
    // default download directory is ~/Downloads on Linux
401
    m_webEngineView->page()->download(resource->url());
402
    resource->accept();
403

Yuri Chornoivan's avatar
Yuri Chornoivan committed
404
    KMessageBox::information(this, i18n("The file has been downloaded successfully at Downloads."), i18n("Download Successful"));
405

406
    disconnect(m_webEngineView->page()->profile(), &QWebEngineProfile::downloadRequested, this, &DocumentationPanelWidget::downloadResource);
407
}