File indexing completed on 2024-04-28 07:39:36

0001 /*.
0002     SPDX-FileCopyrightText: 2007 Vladimir Kuznetsov <ks.vladimir@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "infobrowser.h"
0008 
0009 #include "worldmodel.h"
0010 #include "settings.h"
0011 
0012 #include <QAction>
0013 #include <QCoreApplication>
0014 #include <QDesktopServices>
0015 #include <QFile>
0016 #include <QIcon>
0017 #include <QItemSelectionModel>
0018 #include <QStandardPaths>
0019 #include <QVBoxLayout>
0020 
0021 #include <KIO/Job>
0022 #include <KLocalizedString>
0023 #include <KToolBar>
0024 
0025 InfoBrowser::InfoBrowser(WorldModel* worldModel, QWidget* parent)
0026     : QDockWidget(i18n("Context Info"), parent),
0027       _worldModel(worldModel), _selectionChanged(false)
0028 {
0029     QWidget* widget = new QWidget(this);
0030     setWidget(widget);
0031 
0032     QVBoxLayout* layout = new QVBoxLayout(widget);
0033     layout->setContentsMargins(0,0,0,0);
0034     layout->setSpacing(0);
0035 
0036     _toolBar = new KToolBar(widget);
0037     layout->addWidget(_toolBar);
0038     _toolBar->setMovable(false);
0039     _toolBar->setFloatable(false);
0040     _toolBar->setIconDimensions(16);
0041     _toolBar->setContextMenuPolicy(Qt::NoContextMenu);
0042     _toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
0043 
0044     _backAction = _toolBar->addAction(QIcon::fromTheme(QStringLiteral("go-previous")), i18n("Back"), this, SLOT(back()));
0045     _backAction->setEnabled(false);
0046     _forwardAction = _toolBar->addAction(QIcon::fromTheme(QStringLiteral("go-next")), i18n("Forward"), this, SLOT(forward()));
0047     _forwardAction->setEnabled(false);
0048 
0049     _toolBar->addSeparator();
0050     _syncAction = _toolBar->addAction(QIcon::fromTheme(QStringLiteral("goto-page")), i18n("Sync selection"), this, SLOT(syncSelection())); // XXX: icon
0051     _syncAction->setEnabled(false);
0052     _followAction = _toolBar->addAction(QIcon::fromTheme(QStringLiteral("note2")), i18n("Follow selection")/*, this, SLOT(syncSelection(bool))*/); // XXX: icon
0053     _followAction->setCheckable(true);
0054     _followAction->setChecked(true);
0055 
0056     _toolBar->addSeparator();
0057     _execAction = _toolBar->addAction(QIcon::fromTheme(QStringLiteral("system-run")), i18n("Open in Browser"), this, SLOT(openInBrowser()));
0058     _execAction->setEnabled(false);
0059 
0060     _htmlBrowser = new QTextBrowser(widget);
0061     _htmlBrowser->setOpenLinks(false);
0062     layout->addWidget(_htmlBrowser);
0063 
0064     connect(_htmlBrowser, &QTextBrowser::anchorClicked, this, [=](const QUrl &url){
0065         openUrl(url);
0066     });
0067 
0068     connect(_worldModel->selectionModel(), &QItemSelectionModel::currentChanged,
0069                                            this, &InfoBrowser::worldCurrentChanged);
0070 
0071     syncSelection();
0072 }
0073 
0074 void InfoBrowser::showEvent(QShowEvent* event)
0075 {
0076     QDockWidget::showEvent(event);
0077     if(_selectionChanged) {
0078         _selectionChanged = false;
0079         QModelIndex current = _worldModel->selectionModel()->currentIndex();
0080         worldCurrentChanged(current, QModelIndex());
0081     }
0082 }
0083 
0084 void InfoBrowser::worldCurrentChanged(const QModelIndex& /*current*/, const QModelIndex& /*previous*/)
0085 {
0086     if(isVisible()) {
0087         if(_followAction->isChecked()) syncSelection();
0088         else updateSyncSelection();
0089     } else {
0090         _selectionChanged = true;
0091     }
0092 }
0093 
0094 void InfoBrowser::syncSelection(bool checked)
0095 {
0096     if(checked) {
0097         const QModelIndex current = _worldModel->selectionModel()->currentIndex();
0098         const QUrl url(QStringLiteral("objinfo:").append(current.data(WorldModel::ClassNameRole).toString()));
0099         openUrl(url, true);
0100     }
0101 }
0102 
0103 void InfoBrowser::updateSyncSelection()
0104 {
0105     if(_htmlBrowser->source().scheme() == QLatin1String("objinfo")) {
0106         QModelIndex current = _worldModel->selectionModel()->currentIndex();
0107         if(_htmlBrowser->source().path() == current.data(WorldModel::ClassNameRole).toString()) {
0108             _syncAction->setEnabled(false);
0109             return;
0110         }
0111     }
0112     _syncAction->setEnabled(true);
0113 }
0114 
0115 void InfoBrowser::openUrl(const QUrl& url, bool clearHistory, bool fromHistory)
0116 {
0117     if(url.scheme() == QLatin1String("objinfo")) {
0118         if(clearHistory) {
0119             _forwardHistory.clear();
0120             _forwardAction->setEnabled(false);
0121             _backHistory.clear();
0122             _backAction->setEnabled(false);
0123             fromHistory = true;
0124         }
0125         QString className = url.path();
0126         if(className.isEmpty()) {
0127             setHtml("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />"
0128                     "</head><body>\n"
0129                     "<div id='doc_box' class='box'>\n"
0130                         "<div id='doc_box-header' class='box-header'>\n"
0131                             "<span id='doc_box-header-title' class='box-header-title'>\n"
0132                             + i18n( "Documentation" ) +
0133                             "</span>\n"
0134                         "</div>\n"
0135                         "<div id='doc_box-body' class='box-body'>\n"
0136                             "<div class='info'><p>\n"
0137                             + i18n("No current object.") +
0138                             "</p></div>\n"
0139                         "</div>\n"
0140                     "</div>\n"
0141                     "</body></html>", fromHistory, url );
0142             return;
0143         }
0144         QString fileName = KLocalizedString::localizedFilePath(QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, QStringLiteral("objinfo/%1.html").arg(className.toLower())));
0145         if(!fileName.isEmpty()) {
0146             QFile file(fileName);
0147             if(file.open(QIODevice::ReadOnly | QIODevice::Text)) {
0148                 setHtml(QString::fromUtf8(file.readAll()), fromHistory, url);
0149                 return;
0150             } else {
0151                 qWarning() << "Could not open help file at location:" << fileName;
0152             }
0153         }
0154         setHtml("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />"
0155                 "</head><body>\n"
0156                 "<div id='doc_box' class='box'>\n"
0157                     "<div id='doc_box-header' class='box-header'>\n"
0158                         "<span id='doc_box-header-title' class='box-header-title'>\n"
0159                         + i18n( "Documentation error" ) +
0160                         "</span>\n"
0161                     "</div>\n"
0162                     "<div id='doc_box-body' class='box-body'>\n"
0163                         "<div class='error'><p>\n"
0164                         + i18n("Documentation for %1 not available. ", QCoreApplication::translate("ObjectClass", className.toUtf8().constData()))
0165                         + i18n("You can help <a href=\"https://edu.kde.org/step\">Step</a> by writing it!") +
0166                         "</p></div>\n"
0167                     "</div>\n"
0168                 "</div>\n"
0169                 "</body></html>", fromHistory, url );
0170         show();
0171     } else if(url.scheme() == QLatin1String("https") || url.scheme() == QLatin1String("http")) {
0172         // do not clear history when open external URL
0173         QDesktopServices::openUrl(url);
0174     } else {
0175         qWarning() << "Unknown URL scheme detected, skipping:" << url;
0176     }
0177 }
0178 
0179 void InfoBrowser::setHtml(const QString& data, bool fromHistory, const QUrl& url)
0180 {
0181     if(!fromHistory) {
0182         _forwardAction->setEnabled(false);
0183         _forwardHistory.clear();
0184 
0185         QString oldUrl = _htmlBrowser->source().url();
0186         if(!oldUrl.isEmpty()) {
0187             _backHistory << oldUrl;
0188             _backAction->setEnabled(true);
0189         }
0190     }
0191 
0192     if(url.scheme() == QLatin1String("http")) {
0193         _execAction->setEnabled(true);
0194     }
0195     else {
0196         _execAction->setEnabled(false);
0197     }
0198 
0199     _htmlBrowser->setSource(url);
0200     _htmlBrowser->setHtml(data);
0201 
0202     updateSyncSelection();
0203 }
0204 
0205 void InfoBrowser::back()
0206 {
0207     Q_ASSERT(!_backHistory.isEmpty());
0208 
0209     QString url(_backHistory.takeLast());
0210     if(_backHistory.isEmpty())
0211         _backAction->setEnabled(false);
0212 
0213     QString curUrl = _htmlBrowser->source().url();
0214     if(!curUrl.isEmpty()) {
0215         _forwardHistory << curUrl;
0216         _forwardAction->setEnabled(true);
0217     }
0218 
0219     openUrl(QUrl(url), false, true);
0220 }
0221 
0222 void InfoBrowser::forward()
0223 {
0224     Q_ASSERT(!_forwardHistory.isEmpty());
0225 
0226     QString url(_forwardHistory.takeLast());
0227     if(_forwardHistory.isEmpty())
0228         _forwardAction->setEnabled(false);
0229 
0230     QString curUrl = _htmlBrowser->source().url();
0231     if(!curUrl.isEmpty()) {
0232         _backHistory << curUrl;
0233         _backAction->setEnabled(true);
0234     }
0235 
0236     openUrl(QUrl(url), false, true);
0237 }
0238 
0239 void InfoBrowser::openInBrowser()
0240 {
0241     if(_htmlBrowser->source().scheme() == QLatin1String("https")) {
0242         QDesktopServices::openUrl(_htmlBrowser->source());
0243     }
0244 }
0245 
0246 #include "moc_infobrowser.cpp"