File indexing completed on 2024-05-19 12:55:57
0001 /*************************************************************************** 0002 Copyright (C) 2005-2007 by Holger Danielsson (holger.danielsson@t-online.de) 0003 (C) 2014-2022 by Michel Ludwig (michel.ludwig@kdemail.net) 0004 ***************************************************************************/ 0005 0006 /*************************************************************************** 0007 * * 0008 * This program is free software; you can redistribute it and/or modify * 0009 * it under the terms of the GNU General Public License as published by * 0010 * the Free Software Foundation; either version 2 of the License, or * 0011 * (at your option) any later version. * 0012 * * 0013 ***************************************************************************/ 0014 0015 #include "dialogs/texdocumentationdialog.h" 0016 #include "kileconstants.h" 0017 #include "kiledebug.h" 0018 0019 #include <KConfigGroup> 0020 #include <KIO/ApplicationLauncherJob> 0021 #include <KIO/JobUiDelegateFactory> 0022 #include <KLocalizedString> 0023 #include <KJobUiDelegate> 0024 #include <KMessageBox> 0025 #include <KApplicationTrader> 0026 #include <KProcess> 0027 #include <KService> 0028 0029 #include <QBoxLayout> 0030 #include <QDialogButtonBox> 0031 #include <QDir> 0032 #include <QEvent> 0033 #include <QFile> 0034 #include <QFileInfo> 0035 #include <QGroupBox> 0036 #include <QHBoxLayout> 0037 #include <QKeyEvent> 0038 #include <QLabel> 0039 #include <QLayout> 0040 #include <QLineEdit> 0041 #include <QMimeDatabase> 0042 #include <QMimeType> 0043 #include <QPushButton> 0044 #include <QRegExp> 0045 #include <QTemporaryFile> 0046 #include <QTreeWidget> 0047 #include <QUrl> 0048 #include <QVBoxLayout> 0049 0050 namespace KileDialog 0051 { 0052 0053 TexDocDialog::TexDocDialog(QWidget *parent) 0054 : QDialog(parent) 0055 , m_buttonBox(new QDialogButtonBox(QDialogButtonBox::RestoreDefaults|QDialogButtonBox::Close)) 0056 , m_tempfile(Q_NULLPTR) 0057 , m_proc(Q_NULLPTR) 0058 { 0059 setWindowTitle(i18n("Documentation Browser")); 0060 setModal(true); 0061 QVBoxLayout *mainLayout = new QVBoxLayout; 0062 setLayout(mainLayout); 0063 0064 // listview 0065 m_texdocs = new QTreeWidget(this); 0066 mainLayout->addWidget(m_texdocs); 0067 m_texdocs->setRootIsDecorated(true); 0068 m_texdocs->setHeaderLabel(i18n("Table of Contents")); 0069 0070 // groupbox 0071 QGroupBox *groupbox = new QGroupBox(i18n("Search"), this); 0072 mainLayout->addWidget(groupbox); 0073 QHBoxLayout *groupboxLayout = new QHBoxLayout(); 0074 groupboxLayout->setAlignment(Qt::AlignTop); 0075 groupbox->setLayout(groupboxLayout); 0076 0077 m_leKeywords = new QLineEdit(groupbox); 0078 m_leKeywords->setPlaceholderText("Keyword"); 0079 m_leKeywords->setClearButtonEnabled(true); 0080 m_pbSearch = new QPushButton(i18n("&Search"), groupbox); 0081 0082 groupboxLayout->addWidget(m_leKeywords); 0083 groupboxLayout->addWidget(m_pbSearch); 0084 0085 m_texdocs->setWhatsThis(i18n("<p>A list of the documentation provided by the installed TeX distribution.</p>" 0086 "<p>Double clicking on an item or pressing the space key will open a viewer to show the corresponding file.</p>" 0087 "<p>Items that are grayed out are not installed.</p>")); 0088 m_leKeywords->setWhatsThis(i18n("You can choose a keyword to show only document files that are related to this keyword.")); 0089 m_pbSearch->setWhatsThis(i18n("Start the search for the chosen keyword.")); 0090 m_pbSearch->setEnabled(false); 0091 m_buttonBox->button(QDialogButtonBox::RestoreDefaults)->setWhatsThis(i18n("Reset list to all available documentation files.")); 0092 m_buttonBox->button(QDialogButtonBox::RestoreDefaults)->setText(i18n("Cancel &Search")); 0093 m_buttonBox->button(QDialogButtonBox::RestoreDefaults)->setEnabled(false); 0094 connect(m_buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, 0095 this, &TexDocDialog::slotResetSearch); 0096 0097 // catch some Return/Enter events 0098 m_texdocs->installEventFilter(this); 0099 m_leKeywords->installEventFilter(this); 0100 0101 connect(m_texdocs, &QTreeWidget::itemDoubleClicked, this, &TexDocDialog::slotListViewDoubleClicked); 0102 connect(m_pbSearch, &QPushButton::clicked, this, &TexDocDialog::slotSearchClicked); 0103 connect(m_leKeywords, &QLineEdit::textChanged, this, &TexDocDialog::slotTextChanged); 0104 0105 m_texmfPath.clear(); 0106 m_texmfdocPath.clear(); 0107 m_texdoctkPath.clear(); 0108 0109 connect(this, &TexDocDialog::processFinished, this, &TexDocDialog::slotInitToc); 0110 executeScript( 0111 "kpsewhich --progname=texdoctk --format='other text files' texdoctk.dat && " 0112 "kpsewhich --expand-path='$TEXMF/doc' && " 0113 "kpsewhich --expand-path='$TEXMF'" 0114 ); 0115 0116 mainLayout->addWidget(m_texdocs); 0117 mainLayout->addWidget(groupbox); 0118 mainLayout->addWidget(m_buttonBox); 0119 0120 connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); 0121 connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); 0122 0123 resize(sizeHint() + m_texdocs->sizeHint()); 0124 } 0125 0126 TexDocDialog::~TexDocDialog() 0127 { 0128 delete m_proc; 0129 delete m_tempfile; 0130 } 0131 0132 ////////////////////// TOC ////////////////////// 0133 0134 void TexDocDialog::readToc() 0135 { 0136 // open to read 0137 QFile fin(m_texdoctkPath); 0138 if (!fin.exists() || !fin.open(QIODevice::ReadOnly)) { 0139 KMessageBox::error(this, i18n("Could not read 'texdoctk.dat'.")); 0140 return; 0141 } 0142 0143 // use a textstream to read all data 0144 QString textline; 0145 QTextStream data(&fin); 0146 while (!data.atEnd()) { 0147 textline = data.readLine(); 0148 if (!(textline.isEmpty() || textline[0] == '#')) { 0149 // save the whole entry 0150 m_tocList.append(textline); 0151 0152 // list entries 0,1,basename(2),3 are needed for keyword search 0153 // (key,title,filepath,keywords) 0154 QStringList list = textline.split(';', Qt::KeepEmptyParts); 0155 0156 // get basename of help file 0157 QString basename; 0158 if (list.count() > 2) { 0159 QFileInfo fi(list[2]); 0160 basename = fi.baseName().toLower(); 0161 } 0162 else { 0163 if (list.count() < 2) { 0164 continue; 0165 } 0166 } 0167 QString entry = list[0] + ';' + list[1]; 0168 if (!basename.isEmpty()) { 0169 entry += ';' + basename; 0170 } 0171 if (list.count() > 3) { 0172 entry += ';' + list[3]; 0173 } 0174 m_tocSearchList.append(entry); 0175 } 0176 } 0177 } 0178 0179 void TexDocDialog::showToc(const QString &caption, const QStringList &doclist, bool toc) 0180 { 0181 QString section; 0182 QStringList keylist; 0183 QTreeWidgetItem *itemsection = Q_NULLPTR; 0184 0185 setUpdatesEnabled(false); 0186 m_texdocs->setHeaderLabel(caption); 0187 0188 for (int i = 0; i < doclist.count(); ++i) { 0189 if (doclist[i][0] == '@') { 0190 section = doclist[i]; 0191 itemsection = new QTreeWidgetItem(m_texdocs, QStringList(section.remove(0, 1))); 0192 } 0193 else { 0194 keylist = doclist[i].split(';', Qt::KeepEmptyParts); 0195 if (keylist.size() < 4) { 0196 continue; 0197 } 0198 if (itemsection) { 0199 QTreeWidgetItem *item = new QTreeWidgetItem(itemsection, QStringList() << keylist[1] << keylist[0]); 0200 item->setIcon(0, QIcon::fromTheme(getIconName(keylist[2]))); 0201 0202 QString filename = findFile(keylist[2]); 0203 if(filename.isEmpty()) { 0204 item->setDisabled(true); 0205 } 0206 0207 // save filename in dictionary 0208 m_dictDocuments[keylist[0]] = filename; 0209 } 0210 } 0211 } 0212 setUpdatesEnabled(true); 0213 0214 if (toc) { 0215 m_pbSearch->setEnabled(false); 0216 } 0217 m_buttonBox->button(QDialogButtonBox::RestoreDefaults)->setEnabled(!toc); 0218 m_texdocs->setFocus(); 0219 0220 if (m_texdocs->topLevelItemCount() == 1) { 0221 m_texdocs->expandAll(); 0222 } 0223 } 0224 0225 bool TexDocDialog::eventFilter(QObject *o, QEvent *e) 0226 { 0227 // catch KeyPress events 0228 if (e->type() == QEvent::KeyPress) { 0229 QKeyEvent *kev = static_cast<QKeyEvent*>(e); 0230 0231 // ListView: 0232 // - space: enable start of viewer 0233 // - return: ignore 0234 if(o == m_texdocs) { 0235 if(kev->key() == Qt::Key_Space) { 0236 slotListViewDoubleClicked(m_texdocs->currentItem()); 0237 return true; 0238 } 0239 if(kev->key() == Qt::Key_Return || kev->key() == Qt::Key_Enter) { 0240 return true; 0241 } 0242 } 0243 0244 // LineEdit 0245 // - return: start search, if button is enabled 0246 if (o == m_leKeywords) { 0247 if(kev->key() == Qt::Key_Return || kev->key() == Qt::Key_Enter) { 0248 callSearch(); 0249 return true; 0250 } 0251 } 0252 } 0253 0254 return false; 0255 } 0256 0257 ////////////////////// prepare document file ////////////////////// 0258 0259 QString TexDocDialog::searchFile(const QString &docfilename, const QString &listofpaths, const QString &subdir) 0260 { 0261 const QStringList pathlist = listofpaths.split(LIST_SEPARATOR); 0262 const QString extensions[] = {"", QLatin1String(".gz"), QLatin1String(".bz2")}; 0263 0264 QString filename; 0265 for(const QString& itp : pathlist) { 0266 for(const QString& ite : extensions) { 0267 filename = (subdir.isEmpty()) ? itp + '/' + docfilename + ite 0268 : itp + '/' + subdir + '/' + docfilename + ite; 0269 0270 if(QFile::exists(filename)) { 0271 return filename; 0272 } 0273 } 0274 } 0275 0276 return QString(); 0277 } 0278 0279 QString TexDocDialog::findFile(const QString &docfilename) 0280 { 0281 QString filename = searchFile(docfilename, m_texmfdocPath); 0282 if(filename.isEmpty()) { 0283 // not found: search it elsewhere 0284 filename = searchFile(docfilename, m_texmfPath, "tex"); 0285 if(filename.isEmpty()) { 0286 return QString(); 0287 } 0288 return filename; 0289 } 0290 return filename; 0291 } 0292 0293 void TexDocDialog::showFile(const QString &filename) 0294 { 0295 KILE_DEBUG_MAIN << "\tshow file: " << filename << Qt::endl; 0296 if (QFile::exists(filename)) { 0297 QUrl url; 0298 url.setPath(filename); 0299 0300 KService::List offers = KApplicationTrader::queryByMimeType(getMimeType(filename)); 0301 if(offers.isEmpty()) { 0302 KMessageBox::error(this, i18n("No KDE service found for this file.")); 0303 return; 0304 } 0305 QList<QUrl> lst; 0306 lst.append(url); 0307 auto *job = new KIO::ApplicationLauncherJob(offers.first()); 0308 job->setUrls(lst); 0309 job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this)); 0310 job->setRunFlags(KIO::ApplicationLauncherJob::DeleteTemporaryFiles); 0311 job->start(); 0312 } 0313 } 0314 0315 0316 ////////////////////// Slots ////////////////////// 0317 0318 void TexDocDialog::slotListViewDoubleClicked(QTreeWidgetItem *item) 0319 { 0320 if (!item->parent() || item->isDisabled()) { 0321 return; 0322 } 0323 0324 QString package = item->text(1); 0325 KILE_DEBUG_MAIN << "\tselect child: " << item->text(0) << Qt::endl 0326 << "\tis package: " << package << Qt::endl; 0327 if (!m_dictDocuments.contains(package)) { 0328 return; 0329 } 0330 0331 QString filename = m_dictDocuments[package]; 0332 KILE_DEBUG_MAIN << "\tgot filename:" << filename << Qt::endl; 0333 if(filename.isEmpty()) { 0334 KMessageBox::error(this, i18n("Could not find the documentation file '%1'", filename)); 0335 return; 0336 } 0337 0338 showFile(filename); 0339 } 0340 0341 void TexDocDialog::slotTextChanged(const QString &text) 0342 { 0343 m_pbSearch->setEnabled(! text.trimmed().isEmpty()); 0344 } 0345 0346 void TexDocDialog::slotSearchClicked() 0347 { 0348 QString keyword = m_leKeywords->text().trimmed(); 0349 if (keyword.isEmpty()) { 0350 KMessageBox::error(this, i18n("No keyword given.")); 0351 return; 0352 } 0353 0354 QString section; 0355 bool writesection = true; 0356 QStringList searchlist; 0357 0358 for (int i = 0; i < m_tocList.count(); i++) { 0359 if (m_tocList[i][0] == '@') { 0360 section = m_tocList[i]; 0361 writesection = true; 0362 } 0363 else { 0364 if (i < m_tocSearchList.count() && m_tocSearchList[i].indexOf(keyword, 0, Qt::CaseInsensitive) > -1) { 0365 if (writesection) { 0366 searchlist.append(section); 0367 } 0368 searchlist.append(m_tocList[i]); 0369 writesection = false; 0370 } 0371 } 0372 } 0373 0374 if (searchlist.count() > 0) { 0375 m_texdocs->clear(); 0376 showToc(i18n("Search results for keyword '%1'", keyword), searchlist, false); 0377 } 0378 else { 0379 KMessageBox::error(this, i18n("No documents found for keyword '%1'.", keyword)); 0380 } 0381 } 0382 0383 void TexDocDialog::slotResetSearch() 0384 { 0385 m_leKeywords->setText(QString()); 0386 m_texdocs->clear(); 0387 showToc(i18n("Table of Contents"), m_tocList, true); 0388 } 0389 0390 void TexDocDialog::callSearch() 0391 { 0392 if(m_pbSearch->isEnabled()) { 0393 slotSearchClicked(); 0394 } 0395 } 0396 0397 ////////////////////// execute shell script ////////////////////// 0398 0399 void TexDocDialog::executeScript(const QString &command) 0400 { 0401 if (m_proc) { 0402 delete m_proc; 0403 } 0404 0405 m_proc = new KProcess(); 0406 m_proc->setShellCommand(command); 0407 m_proc->setOutputChannelMode(KProcess::MergedChannels); 0408 m_proc->setReadChannel(QProcess::StandardOutput); 0409 m_output.clear(); 0410 0411 connect(m_proc, &KProcess::readyReadStandardOutput, 0412 this, &TexDocDialog::slotProcessOutput); 0413 connect(m_proc, &KProcess::readyReadStandardError, 0414 this, &TexDocDialog::slotProcessOutput); 0415 connect(m_proc, static_cast<void (QProcess::*)(int,QProcess::ExitStatus)>(&QProcess::finished), 0416 this, &TexDocDialog::slotProcessExited); 0417 0418 KILE_DEBUG_MAIN << "=== TexDocDialog::runShellScript() ====================" << Qt::endl; 0419 KILE_DEBUG_MAIN << " execute: " << command << Qt::endl; 0420 m_proc->start(); 0421 } 0422 0423 void TexDocDialog::slotProcessOutput() 0424 { 0425 m_output += m_proc->readAll(); 0426 } 0427 0428 void TexDocDialog::slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus) 0429 { 0430 Q_UNUSED(exitCode); 0431 0432 if (exitStatus == QProcess::NormalExit) { 0433 //showFile(m_filename); 0434 emit(processFinished()); 0435 } 0436 else { 0437 KMessageBox::error(this, i18n("<center>") + i18n("Could not determine the search paths of TexLive/teTeX or file 'texdoctk.dat'.<br/>" 0438 "Unfortunately, Kile cannot show any useful information.") + i18n("</center>"), i18n("TexDoc Dialog")); 0439 } 0440 } 0441 0442 ////////////////////// process slots, when finished ////////////////////// 0443 0444 void TexDocDialog::slotInitToc() 0445 { 0446 disconnect(this, &TexDocDialog::processFinished, this, &TexDocDialog::slotInitToc); 0447 0448 QStringList results = m_output.split('\n', Qt::KeepEmptyParts); 0449 if (results.count() < 3) { 0450 KMessageBox::error(this, i18n("Could not determine the installation path of your TeX distribution or find the file 'texdoctk.dat'.<br/>" 0451 "Hence, we cannot provide you with an overview of the installed TeX documentation.")); 0452 return; 0453 } 0454 0455 m_texdoctkPath = results[0]; 0456 m_texmfdocPath = results[1]; 0457 m_texmfPath = results[2]; 0458 0459 KILE_DEBUG_MAIN << "\ttexdoctk path: " << m_texdoctkPath << Qt::endl; 0460 KILE_DEBUG_MAIN << "\ttexmfdoc path: " << m_texmfdocPath << Qt::endl; 0461 KILE_DEBUG_MAIN << "\ttexmf path: " << m_texmfPath << Qt::endl; 0462 0463 if(m_texdoctkPath.indexOf('\n', -1) > -1) { 0464 m_texdoctkPath.truncate(m_texdoctkPath.length() - 1); 0465 } 0466 0467 // read data and initialize listview 0468 readToc(); 0469 slotResetSearch(); 0470 } 0471 0472 void TexDocDialog::slotShowFile() 0473 { 0474 disconnect(this, &TexDocDialog::processFinished, this, &TexDocDialog::slotShowFile); 0475 showFile(m_filename); 0476 } 0477 0478 ////////////////////// Icon/Mime ////////////////////// 0479 0480 QString TexDocDialog::getMimeType(const QString &filename) 0481 { 0482 QFileInfo fi(filename); 0483 QString basename = fi.baseName().toLower(); 0484 QString ext = fi.suffix().toLower(); 0485 0486 QString mimetype; 0487 if (ext == "txt" || ext == "faq" || ext == "sty" || basename == "readme" || basename == "00readme") { 0488 mimetype = "text/plain"; 0489 } 0490 else { 0491 QUrl mimeurl; 0492 mimeurl.setPath(filename); 0493 QMimeDatabase db; 0494 QMimeType pMime = db.mimeTypeForUrl(mimeurl); 0495 mimetype = pMime.name(); 0496 } 0497 0498 KILE_DEBUG_MAIN << "\tmime = " << mimetype << " " << Qt::endl; 0499 return mimetype; 0500 } 0501 0502 QString TexDocDialog::getIconName(const QString &filename) 0503 { 0504 QFileInfo fi(filename); 0505 QString basename = fi.baseName().toLower(); 0506 QString ext = fi.suffix().toLower(); 0507 0508 QString icon; 0509 if (ext == "application-x-bzdvi" ) { // FIXME exchange as soon as a real dvi icon is available 0510 icon = ext; 0511 } 0512 else if( ext == "htm" || ext == "html" ) { 0513 icon = "text-html"; 0514 } 0515 else if(ext == "pdf" ) { 0516 icon = "application-pdf"; 0517 } 0518 else if( ext == "txt") { 0519 icon = "text-plain"; 0520 } 0521 else if(ext == "ps") { 0522 icon = "application-postscript"; 0523 } 0524 else if(ext == "sty") { 0525 icon = "text-x-tex"; 0526 } 0527 else if(ext == "faq" || basename == "readme" || basename == "00readme") { 0528 icon = "text-x-readme"; 0529 } 0530 else { 0531 icon = "text-plain"; 0532 } 0533 return icon; 0534 } 0535 0536 }