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"