File indexing completed on 2024-05-05 04:40:04

0001 /*
0002     SPDX-FileCopyrightText: 2010 Yannick Motta <yannick.motta@gmail.com>
0003     SPDX-FileCopyrightText: 2010 Benjamin Port <port.benjamin@gmail.com>
0004     SPDX-FileCopyrightText: 2014 Milian Wolff <mail@milianw.de>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "manpagemodel.h"
0010 #include "manpageplugin.h"
0011 #include "manpagedocumentation.h"
0012 #include "debug.h"
0013 
0014 #include "../openwith/iopenwith.h"
0015 
0016 #include <interfaces/icore.h>
0017 #include <interfaces/idocumentationcontroller.h>
0018 
0019 #include <QDesktopServices>
0020 #include <QStringListModel>
0021 #include <QTimer>
0022 
0023 #include <limits>
0024 
0025 namespace {
0026 
0027 const quintptr INVALID_ID = std::numeric_limits<quintptr>::max();
0028 
0029 }
0030 
0031 using namespace KDevelop;
0032 
0033 ManPageModel::ManPageModel(QObject* parent)
0034     : QAbstractItemModel(parent)
0035     , m_indexModel(new QStringListModel(this))
0036 {
0037     QMetaObject::invokeMethod(const_cast<ManPageModel*>(this), "initModel", Qt::QueuedConnection);
0038 }
0039 
0040 ManPageModel::~ManPageModel()
0041 {
0042 }
0043 
0044 QModelIndex ManPageModel::parent(const QModelIndex& child) const
0045 {
0046     if (child.isValid() && child.column() == 0 && child.internalId() != INVALID_ID) {
0047         return createIndex(child.internalId(), 0, INVALID_ID);
0048     }
0049     return QModelIndex();
0050 }
0051 
0052 QModelIndex ManPageModel::index(int row, int column, const QModelIndex& parent) const
0053 {
0054     if (row < 0 || column != 0) {
0055         return QModelIndex();
0056     } else if (!parent.isValid() && row == m_sectionList.count()) {
0057         return QModelIndex();
0058     }
0059 
0060     return createIndex(row, column, parent.isValid() ? parent.row() : INVALID_ID);
0061 }
0062 
0063 QVariant ManPageModel::data(const QModelIndex& index, int role) const
0064 {
0065     if (index.isValid()) {
0066         if (role == Qt::DisplayRole) {
0067             int internal(index.internalId());
0068             if (internal >= 0) {
0069                 int position = index.row();
0070                 QString sectionUrl = m_sectionList.at(index.internalId()).first;
0071                 return manPage(sectionUrl, position);
0072             } else {
0073                 return m_sectionList.at(index.row()).second;
0074             }
0075         }
0076     }
0077     return QVariant();
0078 }
0079 
0080 int ManPageModel::rowCount(const QModelIndex& parent) const
0081 {
0082     if (!parent.isValid()) {
0083         return m_sectionList.count();
0084     } else if (parent.internalId() == INVALID_ID) {
0085         const QString sectionUrl = m_sectionList.at(parent.row()).first;
0086         return m_manMap.value(sectionUrl).count();
0087     }
0088     return 0;
0089 }
0090 
0091 QString ManPageModel::manPage(const QString& sectionUrl, int position) const
0092 {
0093     return m_manMap.value(sectionUrl).at(position);
0094 }
0095 
0096 void ManPageModel::initModel()
0097 {
0098     m_sectionList.clear();
0099     m_manMap.clear();
0100     auto list = KIO::listDir(QUrl(QStringLiteral("man://")), KIO::HideProgressInfo);
0101     connect(list, &KIO::ListJob::entries, this, &ManPageModel::indexEntries);
0102     connect(list, &KIO::ListJob::result, this, &ManPageModel::indexLoaded);
0103 }
0104 
0105 void ManPageModel::indexEntries(KIO::Job* /*job*/, const KIO::UDSEntryList& entries)
0106 {
0107     for (const KIO::UDSEntry& entry : entries) {
0108         if (entry.isDir()) {
0109             m_sectionList << qMakePair(entry.stringValue(KIO::UDSEntry::UDS_URL), entry.stringValue(KIO::UDSEntry::UDS_NAME));
0110         }
0111     }
0112 }
0113 
0114 void ManPageModel::indexLoaded(KJob* job)
0115 {
0116     if (job->error() != 0) {
0117         m_errorString = job->errorString();
0118         emit error(m_errorString);
0119         return;
0120     }
0121 
0122     emit sectionListUpdated();
0123 
0124     Q_ASSERT(m_nbSectionLoaded == 0);
0125     if (!m_sectionList.isEmpty()) {
0126         initSection();
0127     }
0128 }
0129 
0130 void ManPageModel::initSection()
0131 {
0132     const QString sectionUrl = m_sectionList.at(m_nbSectionLoaded).first;
0133     m_manMap[sectionUrl].clear();
0134     auto list = KIO::listDir(QUrl(sectionUrl), KIO::HideProgressInfo);
0135     connect(list, &KIO::ListJob::entries, this, &ManPageModel::sectionEntries);
0136     connect(list, &KIO::ListJob::result, this, &ManPageModel::sectionLoaded);
0137 }
0138 
0139 void ManPageModel::sectionEntries(KIO::Job* /*job*/, const KIO::UDSEntryList& entries)
0140 {
0141     const QString sectionUrl = m_sectionList.at(m_nbSectionLoaded).first;
0142     auto& pages = m_manMap[sectionUrl];
0143     pages.reserve(pages.size() + entries.size());
0144     for (const KIO::UDSEntry& entry : entries) {
0145         pages << entry.stringValue(KIO::UDSEntry::UDS_NAME);
0146     }
0147 }
0148 
0149 void ManPageModel::sectionLoaded()
0150 {
0151     m_nbSectionLoaded++;
0152     emit sectionParsed();
0153     if (m_nbSectionLoaded < m_sectionList.size()) {
0154         initSection();
0155     } else {
0156         // End of init
0157         m_loaded = true;
0158         m_index.clear();
0159         for (const auto& entries : qAsConst(m_manMap)) {
0160             m_index += entries.toList();
0161         }
0162         m_index.sort();
0163         m_index.removeDuplicates();
0164         m_indexModel->setStringList(m_index);
0165         emit manPagesLoaded();
0166     }
0167 }
0168 
0169 void ManPageModel::showItem(const QModelIndex& idx)
0170 {
0171     if (idx.isValid() && idx.internalId() != INVALID_ID) {
0172         QString sectionUrl = m_sectionList.at(idx.internalId()).first;
0173         QString page = manPage(sectionUrl, idx.row());
0174         IDocumentation::Ptr newDoc(new ManPageDocumentation(page, QUrl(sectionUrl + QLatin1Char('/') + page)));
0175         ICore::self()->documentationController()->showDocumentation(newDoc);
0176     }
0177 }
0178 
0179 void ManPageModel::showItemFromUrl(const QUrl& url)
0180 {
0181     qCDebug(MANPAGE) << "showing" << url.toDisplayString(QUrl::PreferLocalFile);
0182 
0183     auto doc = ManPageDocumentation::s_provider->documentation(url);
0184     IDocumentationController* const controller = ICore::self()->documentationController();
0185     if (!doc) {
0186         doc = controller->documentation(url);
0187         if (!doc) {
0188             // Open the unsupported link externally and stay on the current
0189             // documentation page. Even if this is an external link we can
0190             // download the contents of, our support for website navigation is very poor.
0191             if (url.isLocalFile()) {
0192                 // This is usually a system header file => open it in the internal editor.
0193                 // HACK: the timer delay works around an inexplicable bug that temporarily
0194                 // scales current Documentation view's QWebEnginePage as if its zoomFactor
0195                 // equals 1 when the call to IOpenWith::openFiles() ends up opening a
0196                 // document in DocumentController.
0197                 QTimer::singleShot(100, [url] { IOpenWith::openFiles({url}); });
0198             } else {
0199                 // This is usually a website or mailto link. IOpenWith::openFiles()
0200                 // tends to open it in the internal editor, which is not nice. Let us
0201                 // bypass IOpenWith and open the link in the user's preferred application.
0202                 if (!QDesktopServices::openUrl(url)) {
0203                     qCWarning(MANPAGE) << "couldn't open URL" << url;
0204                 }
0205             }
0206             return;
0207         }
0208     }
0209     controller->showDocumentation(doc);
0210 }
0211 
0212 QStringListModel* ManPageModel::indexList()
0213 {
0214     return m_indexModel;
0215 }
0216 
0217 bool ManPageModel::containsIdentifier(const QString& identifier)
0218 {
0219     return m_index.contains(identifier);
0220 }
0221 
0222 int ManPageModel::sectionCount() const
0223 {
0224     return m_sectionList.count();
0225 }
0226 
0227 bool ManPageModel::isLoaded() const
0228 {
0229     return m_loaded;
0230 }
0231 
0232 int ManPageModel::nbSectionLoaded() const
0233 {
0234     return m_nbSectionLoaded;
0235 }
0236 
0237 bool ManPageModel::identifierInSection(const QString& identifier, const QString& section) const
0238 {
0239     const QString sectionLink = QLatin1String("man:/(") + section + QLatin1Char(')');
0240     for (auto it = m_manMap.begin(); it != m_manMap.end(); ++it) {
0241         if (it.key().startsWith(sectionLink)) {
0242             return it.value().indexOf(identifier) != -1;
0243         }
0244     }
0245     return false;
0246 }
0247 
0248 bool ManPageModel::hasError() const
0249 {
0250     return !m_errorString.isEmpty();
0251 }
0252 
0253 const QString& ManPageModel::errorString() const
0254 {
0255     return m_errorString;
0256 }
0257 
0258 #include "moc_manpagemodel.cpp"