documentationpanelwidget.cpp 14.4 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA  02110-1301, USA.

    ---
    Copyright (C) 2020 Shubham <aryan100jangid@gmail.com>
 */

#include "cantor_macros.h"
#include "documentationpanelplugin.h"

#include <KLocalizedString>
25
#include <KMessageBox>
26

27
#include <QAction>
Shubham  .'s avatar
Shubham . committed
28
#include <QCompleter>
29
#include <QComboBox>
30
#include <QDebug>
Shubham  .'s avatar
Shubham . committed
31
#include <QFrame>
Shubham  .'s avatar
Shubham . committed
32
#include <QHBoxLayout>
33
34
35
36
#include <QHelpContentWidget>
#include <QHelpEngine>
#include <QHelpIndexWidget>
#include <QIcon>
Shubham  .'s avatar
Shubham . committed
37
#include <QLabel>
38
#include <QLineEdit>
Shubham  .'s avatar
Shubham . committed
39
#include <QModelIndex>
40
#include <QPushButton>
41
#include <QShortcut>
42
#include <QStandardPaths>
43
#include <QStackedWidget>
Shubham  .'s avatar
Shubham . committed
44
#include <QToolButton>
Shubham  .'s avatar
Shubham . committed
45
#include <QVBoxLayout>
46
#include <QWebEngineDownloadItem>
Shubham  .'s avatar
Shubham . committed
47
48
#include <QWebEngineProfile>
#include <QWebEngineUrlScheme>
49
#include <QWebEngineView>
50

51
DocumentationPanelWidget::DocumentationPanelWidget(const QString& backend, const QString& backendIcon, QWidget* parent) :QWidget(parent)
52
{
53
54
55
    m_backend = backend;
    m_icon = backendIcon;

56
    const QString& fileName = QStandardPaths::locate(QStandardPaths::AppDataLocation, QLatin1String("documentation/") + m_backend + QLatin1String("/help.qhc"));
Shubham  .'s avatar
Shubham . committed
57

58
    m_engine = new QHelpEngine(fileName, this);
Shubham  .'s avatar
Shubham . committed
59
    m_index = m_engine->indexWidget();
60

61
    if(!m_engine->setupData())
62
    {
63
        qWarning() << "Couldn't setup QtHelp Engine";
64
        qWarning() << m_engine->error();
65
    }
66

Shubham  .'s avatar
Shubham . committed
67
68
69
70
71
    if(m_backend != QLatin1String("Octave"))
    {
      m_engine->setProperty("_q_readonly", QVariant::fromValue<bool>(true));
    }

72
73
    loadDocumentation();

74
75
76
77
78
79
80
81
82
83
    m_textBrowser = new QWebEngineView(this);

    // Register custom scheme handler for qthelp:// scheme
    static bool qthelpRegistered = false;

    if(!qthelpRegistered)
    {
        QWebEngineUrlScheme qthelp("qthelp");
        QWebEngineUrlScheme::registerScheme(qthelp);
        m_textBrowser->page()->profile()->installUrlSchemeHandler("qthelp", new QtHelpSchemeHandler(m_engine));
84
85
86
87
88
        m_textBrowser->page()->action(QWebEnginePage::ViewSource)->setVisible(false);
        m_textBrowser->page()->action(QWebEnginePage::OpenLinkInNewTab)->setVisible(false);
        m_textBrowser->page()->action(QWebEnginePage::OpenLinkInNewWindow)->setVisible(false);
        m_textBrowser->page()->action(QWebEnginePage::DownloadLinkToDisk)->setVisible(false);
        m_textBrowser->page()->action(QWebEnginePage::Reload)->setVisible(false);
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
        qthelpRegistered = true;
    }

    // set initial page contents, otherwise page is blank
    if(m_backend == QLatin1String("Maxima"))
    {
        m_textBrowser->load(QUrl(QLatin1String("qthelp://org.kde.cantor/doc/maxima.html")));
        m_textBrowser->show();
    }
    else if(m_backend == QLatin1String("Octave"))
    {
        m_textBrowser->load(QUrl(QLatin1String("qthelp://org.octave.interpreter-1.0/doc/octave.html/index.html")));
        m_textBrowser->show();
    }

Shubham  .'s avatar
Shubham . committed
104
105
106
    /////////////////////////
    // Top toolbar layout //
    ///////////////////////
107
    QPushButton* home = new QPushButton(this);
Shubham  .'s avatar
Shubham . committed
108
109
    home->setIcon(QIcon::fromTheme(QLatin1String("go-home")));
    home->setToolTip(i18nc("@button go to contents page", "Go to the contents"));
110
    home->setEnabled(false);
111

112
    QComboBox* documentationSelector = new QComboBox(this);
Shubham  .'s avatar
Shubham . committed
113
    // iterate through the available docs for current backend, for example python may have matplotlib, scikitlearn etc
114
    documentationSelector->addItem(QIcon::fromTheme(m_icon), m_backend);
115

Shubham  .'s avatar
Shubham . committed
116
117
118
119
120
121
122
123
    // real time searcher
    m_search = new QLineEdit(this);
    m_search->setPlaceholderText(i18nc("@info:placeholder", "Search through keywords..."));
    m_search->setClearButtonEnabled(true);
    m_search->setCompleter(new QCompleter(m_index->model(), m_search));
    m_search->completer()->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
    m_search->completer()->setCaseSensitivity(Qt::CaseInsensitive);

Shubham  .'s avatar
Shubham . committed
124
125
126
127
    // Add a seperator
    QFrame *seperator = new QFrame(this);
    seperator->setFrameShape(QFrame::VLine);
    seperator->setFrameShadow(QFrame::Sunken);
128

129
    QPushButton* findPage = new QPushButton(this);
130
    findPage->setEnabled(false);
Shubham  .'s avatar
Shubham . committed
131
132
    findPage->setIcon(QIcon::fromTheme(QLatin1String("edit-find")));
    findPage->setToolTip(i18nc("@info:tooltip", "Find in text of current documentation page"));
133
    findPage->setShortcut(QKeySequence(/*Qt::CTRL + */Qt::Key_F3));
134

135
136
137
138
139
    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
140
141
142
143
144
145
146
    QHBoxLayout* layout = new QHBoxLayout(this);
    layout->addWidget(home);
    layout->addWidget(documentationSelector);
    layout->addWidget(m_search);
    layout->addWidget(seperator);
    layout->addWidget(findPage);
    layout->addWidget(resetZoom);
147

Shubham  .'s avatar
Shubham . committed
148
149
    QWidget* toolBarContainer = new QWidget(this);
    toolBarContainer->setLayout(layout);
Shubham  .'s avatar
Shubham . committed
150

151
152
153
    // 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
154

155
156
    connect(zoomIn, &QShortcut::activated, this, [=]{
        m_textBrowser->setZoomFactor(m_textBrowser->zoomFactor() + 0.1);
Shubham  .'s avatar
Shubham . committed
157
        emit zoomFactorChanged();
158
159
160
161
    });

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

163
164
    connect(zoomOut, &QShortcut::activated, this, [=]{
        m_textBrowser->setZoomFactor(m_textBrowser->zoomFactor() - 0.1);
Shubham  .'s avatar
Shubham . committed
165
        emit zoomFactorChanged();
166
    });
Shubham  .'s avatar
Shubham . committed
167

Shubham  .'s avatar
Shubham . committed
168
169
170
171
172
173
    connect(this, &DocumentationPanelWidget::zoomFactorChanged, [=]{
        if(m_textBrowser->zoomFactor() != 1.0)
            resetZoom->setEnabled(true);
        else
            resetZoom->setEnabled(false);
    });
Shubham  .'s avatar
Shubham . committed
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189

    /////////////////////////
    // Display area layout//
    ///////////////////////
    m_displayArea = new QStackedWidget(this);
    m_displayArea->addWidget(m_engine->contentWidget());
    m_displayArea->addWidget(m_textBrowser);

    /* Adding the index widget to implement the logic for context sensitive help
     * This widget would be NEVER shown*/
    m_displayArea->addWidget(m_index);

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

Shubham  .'s avatar
Shubham . committed
190
191
192
193
194
195
196
197
    // 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:"));

198
199
200
    m_findText = new QLineEdit(this);
    m_findText->setPlaceholderText(i18nc("@info:placeholder", "Search..."));
    m_findText->setClearButtonEnabled(true);
Shubham  .'s avatar
Shubham . committed
201
202

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

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

210
211
212
    m_matchCase = new QToolButton(this);
    m_matchCase->setIcon(QIcon::fromTheme(QLatin1String("format-text-superscript")));
    m_matchCase->setToolTip(i18nc("@info:tooltip", "Match case sensitive"));
213
    m_matchCase->setCheckable(true);
Shubham  .'s avatar
Shubham . committed
214

215
    // Create a layout for find in text widgets
Shubham  .'s avatar
Shubham . committed
216
217
218
    QHBoxLayout* lout = new QHBoxLayout(this);
    lout->addWidget(hideButton);
    lout->addWidget(label);
219
    lout->addWidget(m_findText);
Shubham  .'s avatar
Shubham . committed
220
221
    lout->addWidget(next);
    lout->addWidget(previous);
222
    lout->addWidget(m_matchCase);
Shubham  .'s avatar
Shubham . committed
223
224
225
226
227

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

Shubham  .'s avatar
Shubham . committed
228
229
230
231
232
    // Add topmost toolbar, display area and find in page widget in a Vertical layout
    QVBoxLayout* vlayout = new QVBoxLayout(this);
    vlayout->addWidget(toolBarContainer);
    vlayout->addWidget(m_displayArea);
    vlayout->addWidget(findPageWidgetContainer);
233

234
    //TODO QHelpIndexWidget::linkActivated is obsolete, use QHelpIndexWidget::documentActivated instead
235
    // display the documentation browser whenever contents are clicked
Shubham  .'s avatar
Shubham . committed
236
    connect(m_engine->contentWidget(), &QHelpContentWidget::linkActivated, [=]{
237
238
239
240
        m_displayArea->setCurrentIndex(1);
    });

    connect(this, &DocumentationPanelWidget::activateBrowser, [=]{
Shubham  .'s avatar
Shubham . committed
241
        m_textBrowser->hide();
242
        m_displayArea->setCurrentIndex(1);
243
        m_textBrowser->show();
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
    });

    connect(m_displayArea, &QStackedWidget::currentChanged, [=]{
        //disable Home and Search in Page buttons when stackwidget shows contents widget, enable when shows web browser
        if(m_displayArea->currentIndex() != 1) //0->contents 1->browser
        {
            findPage->setEnabled(false);
            home->setEnabled(false);
        }
        else
        {
            findPage->setEnabled(true);
            home->setEnabled(true);
        }
    });

    connect(home, &QPushButton::clicked, [=]{
        m_displayArea->setCurrentIndex(0);
262
        findPageWidgetContainer->hide();
263
264
    });

265
266
    connect(resetZoom, &QPushButton::clicked, [=]{
        m_textBrowser->setZoomFactor(1.0);
Shubham  .'s avatar
Shubham . committed
267
        resetZoom->setEnabled(false);
268
269
    });

270
    connect(m_engine->contentWidget(), &QHelpContentWidget::linkActivated, this, &DocumentationPanelWidget::displayHelp);
271
    connect(m_index, &QHelpIndexWidget::linkActivated, this, &DocumentationPanelWidget::displayHelp);
272
    connect(m_search, &QLineEdit::returnPressed, this, &DocumentationPanelWidget::returnPressed);
Shubham  .'s avatar
Shubham . committed
273
    connect(m_search->completer(), QOverload<const QModelIndex&>::of(&QCompleter::activated), this, &DocumentationPanelWidget::returnPressed);
274

275
    // connect statements for Find in Page text widget
Shubham  .'s avatar
Shubham . committed
276
277
278
279
280
281
    connect(findPage, &QPushButton::clicked, [=]{
        findPageWidgetContainer->show();
        m_findText->clear();
        m_findText->setFocus();
    });

282
283
    connect(hideButton, &QToolButton::clicked, this, [=]{
        findPageWidgetContainer->hide();
284
        m_textBrowser->findText(QString()); // this clears up the selected text
285
286
    });

287
288
289
290
291
    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);
292
293
    connect(m_matchCase, &QAbstractButton::toggled, this, [=]{
        m_textBrowser->findText(QString());
294
        searchForward();
295
    });
296
297
298

    // for webenginebrowser for downloading of images or html pages
    connect(m_textBrowser->page()->profile(), &QWebEngineProfile::downloadRequested, this, &DocumentationPanelWidget::downloadResource);
299
300
}

Shubham  .'s avatar
Shubham . committed
301
302
303
304
DocumentationPanelWidget::~DocumentationPanelWidget()
{
    delete m_engine;
    delete m_textBrowser;
305
    delete m_displayArea;
306
    delete m_search;
307
308
    delete m_findText;
    delete m_matchCase;
Shubham  .'s avatar
Shubham . committed
309
    //delete m_index; this crashes
Shubham  .'s avatar
Shubham . committed
310
311
}

312
313
314
315
316
317
void DocumentationPanelWidget::setBackend(const QString& backend)
{
    m_backend = backend;
}

void DocumentationPanelWidget::setBackendIcon(const QString& icon)
318
{
319
    m_icon = icon;
320
321
}

322
323
void DocumentationPanelWidget::displayHelp(const QUrl& url)
{
Shubham  .'s avatar
Shubham . committed
324
    m_textBrowser->load(url);
325
    m_textBrowser->show();
326
327
328
329
330
331
}

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

Shubham  .'s avatar
Shubham . committed
332
333
    if (input.isEmpty())
        return;
334

Shubham  .'s avatar
Shubham . committed
335
336
    /*auto model = m_index->model();
    //auto model = m_engine->indexModel();
337
338
339
340

    for(int row = 0; row < model->rowCount(); ++row)
    {
        auto keyword = model->index(row, 0);
Shubham  .'s avatar
Shubham . committed
341
        if(keyword.data().toString() == input)
342
        {
Shubham  .'s avatar
Shubham . committed
343

344
345
            break;
        }
Shubham  .'s avatar
Shubham . committed
346
    }*/
347

348
    contextSensitiveHelp(input);
349
350
}

351
void DocumentationPanelWidget::contextSensitiveHelp(const QString& keyword)
352
{
Shubham  .'s avatar
Shubham . committed
353
    // First make sure we have display browser as the current widget on the QStackedWidget
354
    emit activateBrowser();
355

356
357
    m_index->filterIndices(keyword); // filter exactly, no wildcards
    m_index->activateCurrentItem(); // this internally emitts the QHelpIndexWidget::linkActivated signal
358
359
}

360
361
362
363
364
365
366
367
368
369
370
371
void DocumentationPanelWidget::searchForward()
{
    m_matchCase->isChecked() ? m_textBrowser->findText(m_findText->text(), QWebEnginePage::FindCaseSensitively) :
                               m_textBrowser->findText(m_findText->text());
}

void DocumentationPanelWidget::searchBackward()
{
    m_matchCase->isChecked() ? m_textBrowser->findText(m_findText->text(), QWebEnginePage::FindCaseSensitively | QWebEnginePage::FindBackward) :
                               m_textBrowser->findText(m_findText->text(), QWebEnginePage::FindBackward);
}

372
373
374
375
376
void DocumentationPanelWidget::downloadResource(QWebEngineDownloadItem* resource)
{
    // default download directory is ~/Downloads on Linux
    m_textBrowser->page()->download(resource->url());
    resource->accept();
377
378
379

    KMessageBox::information(this, i18n("The file has been downloaded successfully at Downloads."), i18n("Download Successfull"));

380
381
382
    disconnect(m_textBrowser->page()->profile(), &QWebEngineProfile::downloadRequested, this, &DocumentationPanelWidget::downloadResource);
}

383
384
void DocumentationPanelWidget::loadDocumentation()
{
Shubham  .'s avatar
Shubham . committed
385
    const QString& fileName = QStandardPaths::locate(QStandardPaths::AppDataLocation, QLatin1String("documentation/") + m_backend + QLatin1String("/help.qch"));
386
    const QString& nameSpace = QHelpEngineCore::namespaceName(fileName);
387

Shubham  .'s avatar
Shubham . committed
388
389
    if(nameSpace.isEmpty() || !m_engine->registeredDocumentations().contains(nameSpace))
    {
Shubham  .'s avatar
Shubham . committed
390
391
        if(!m_engine->registerDocumentation(fileName))
            qWarning() << m_engine->error();
Shubham  .'s avatar
Shubham . committed
392
    }
393
}