1 /***************************************************************************
2  *   Copyright (C) 2004-2018 by Thomas Fischer <fischer@unix-ag.uni-kl.de> *
3  *                                                                         *
4  *   This program is free software; you can redistribute it and/or modify  *
5  *   it under the terms of the GNU General Public License as published by  *
6  *   the Free Software Foundation; either version 2 of the License, or     *
7  *   (at your option) any later version.                                   *
8  *                                                                         *
9  *   This program is distributed in the hope that it will be useful,       *
10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
12  *   GNU General Public License for more details.                          *
13  *                                                                         *
14  *   You should have received a copy of the GNU General Public License     *
15  *   along with this program; if not, see <https://www.gnu.org/licenses/>. *
16  ***************************************************************************/
17 
18 #include "documentpreview.h"
19 
20 #include <typeinfo>
21 
22 #include <QDomDocument>
23 #include <QDomElement>
24 #include <QList>
25 #include <QLayout>
26 #include <QMap>
27 #include <QFileInfo>
28 #include <QResizeEvent>
29 #include <QCheckBox>
30 #include <QMenuBar>
31 #include <QStackedWidget>
32 #include <QDockWidget>
33 #include <QDebug>
34 #include <QPushButton>
35 #include <QMutex>
36 #include <QMimeDatabase>
37 #include <QMimeType>
38 #include <QIcon>
39 #ifdef HAVE_WEBENGINEWIDGETS
40 #include <QWebEngineView>
41 #else // HAVE_WEBENGINEWIDGETS
42 #ifdef HAVE_WEBKITWIDGETS
43 #include <QWebView>
44 #endif // HAVE_WEBKITWIDGETS
45 #endif // HAVE_WEBENGINEWIDGETS
46 
47 #include <KLocalizedString>
48 #include <KComboBox>
49 #include <KJobWidgets>
50 #include <KRun>
51 #include <KMimeTypeTrader>
52 #include <KService>
53 #include <KParts/Part>
54 #include <KParts/ReadOnlyPart>
55 #include <kio/jobclasses.h>
56 #include <kio/job.h>
57 #include <kio/jobuidelegate.h>
58 #include <KToolBar>
59 #include <KActionCollection>
60 #include <KSharedConfig>
61 #include <KConfigGroup>
62 #include <kio_version.h>
63 
64 #include "kbibtex.h"
65 #include "element.h"
66 #include "entry.h"
67 #include "file.h"
68 #include "fileinfo.h"
69 #include "logging_program.h"
70 
ImageLabel(const QString & text,QWidget * parent,Qt::WindowFlags f)71 ImageLabel::ImageLabel(const QString &text, QWidget *parent, Qt::WindowFlags f)
72         : QLabel(text, parent, f)
73 {
74     /// nothing
75 }
76 
setPixmap(const QPixmap & pixmap)77 void ImageLabel::setPixmap(const QPixmap &pixmap)
78 {
79     m_pixmap = pixmap;
80     if (!m_pixmap.isNull()) {
81         setCursor(Qt::WaitCursor);
82         QPixmap scaledPixmap = m_pixmap.width() <= width() && m_pixmap.height() <= height() ? m_pixmap : pixmap.scaled(width(), height(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
83         QLabel::setPixmap(scaledPixmap);
84         setMinimumSize(100, 100);
85         unsetCursor();
86     } else
87         QLabel::setPixmap(m_pixmap);
88 }
89 
resizeEvent(QResizeEvent * event)90 void ImageLabel::resizeEvent(QResizeEvent *event)
91 {
92     QLabel::resizeEvent(event);
93     if (!m_pixmap.isNull()) {
94         setCursor(Qt::WaitCursor);
95         QPixmap scaledPixmap = m_pixmap.width() <= event->size().width() && m_pixmap.height() <= event->size().height() ? m_pixmap : m_pixmap.scaled(event->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
96         QLabel::setPixmap(scaledPixmap);
97         setMinimumSize(100, 100);
98         unsetCursor();
99     }
100 }
101 
102 class DocumentPreview::DocumentPreviewPrivate
103 {
104 public:
105     struct UrlInfo {
106         QUrl url;
107         QString mimeType;
108         QIcon icon;
109     };
110 
111 private:
112     DocumentPreview *p;
113 
114     KSharedConfigPtr config;
115     static const QString configGroupName;
116     static const QString onlyLocalFilesCheckConfig;
117 
118     QPushButton *externalViewerButton;
119     QStackedWidget *stackedWidget;
120     ImageLabel *message;
121     QMap<int, struct UrlInfo> cbxEntryToUrlInfo;
122     QMutex addingUrlMutex;
123 
124     static const QString arXivPDFUrlStart;
125     bool anyLocal;
126 
127     QMenuBar *menuBar;
128     KToolBar *toolBar;
129     KParts::ReadOnlyPart *okularPart;
130 #ifdef HAVE_WEBENGINEWIDGETS
131     QWebEngineView *htmlWidget;
132 #else // HAVE_WEBENGINEWIDGETS
133 #ifdef HAVE_WEBKITWIDGETS
134     QWebView *htmlWidget;
135 #else // HAVE_WEBKITWIDGETS
136     KParts::ReadOnlyPart *htmlPart;
137 #endif // HAVE_WEBKITWIDGETS
138 #endif // HAVE_WEBENGINEWIDGETS
139     int swpMessage, swpOkular, swpHTML;
140 
141 public:
142     KComboBox *urlComboBox;
143     QPushButton *onlyLocalFilesButton;
144     QList<KIO::StatJob *> runningJobs;
145     QSharedPointer<const Entry> entry;
146     QUrl baseUrl;
147     bool anyRemote;
148 
locatePart(const QString & mimeType,QWidget * parentWidget)149     KParts::ReadOnlyPart *locatePart(const QString &mimeType, QWidget *parentWidget) {
150         KService::Ptr service = KMimeTypeTrader::self()->preferredService(mimeType, QStringLiteral("KParts/ReadOnlyPart"));
151         if (service) {
152             KParts::ReadOnlyPart *part = service->createInstance<KParts::ReadOnlyPart>(parentWidget, p);
153             connect(part, static_cast<void(KParts::ReadOnlyPart::*)()>(&KParts::ReadOnlyPart::completed), p, &DocumentPreview::loadingFinished);
154             return part;
155         } else
156             return nullptr;
157     }
158 
DocumentPreviewPrivate(DocumentPreview * parent)159     DocumentPreviewPrivate(DocumentPreview *parent)
160             : p(parent), config(KSharedConfig::openConfig(QStringLiteral("kbibtexrc"))), anyLocal(false), entry(nullptr), anyRemote(false) {
161         setupGUI();
162     }
163 
164     /**
165       * Create user interface for this widget.
166       * It consists of some controlling widget on the top,
167       * but the most space is consumed by KPart widgets
168       * inside a QStackedWidget to show the external content
169       * (PDF file, web page, ...).
170       */
setupGUI()171     void setupGUI() {
172         QVBoxLayout *layout = new QVBoxLayout(p);
173         layout->setMargin(0);
174 
175         /// some widgets on the top to control the view
176 
177         QHBoxLayout *innerLayout = new QHBoxLayout();
178         layout->addLayout(innerLayout, 0);
179 
180         onlyLocalFilesButton = new QPushButton(QIcon::fromTheme(QStringLiteral("applications-internet")), QString(), p);
181         onlyLocalFilesButton->setToolTip(i18n("Toggle between local files only and all documents including remote ones"));
182         innerLayout->addWidget(onlyLocalFilesButton, 0);
183         onlyLocalFilesButton->setCheckable(true);
184         QSizePolicy sp = onlyLocalFilesButton->sizePolicy();
185         sp.setVerticalPolicy(QSizePolicy::MinimumExpanding);
186         onlyLocalFilesButton->setSizePolicy(sp);
187 
188         urlComboBox = new KComboBox(false, p);
189         innerLayout->addWidget(urlComboBox, 1);
190 
191         externalViewerButton = new QPushButton(QIcon::fromTheme(QStringLiteral("document-open")), QString(), p);
192         externalViewerButton->setToolTip(i18n("Open in external program"));
193         innerLayout->addWidget(externalViewerButton, 0);
194         sp = externalViewerButton->sizePolicy();
195         sp.setVerticalPolicy(QSizePolicy::MinimumExpanding);
196         externalViewerButton->setSizePolicy(sp);
197 
198         menuBar = new QMenuBar(p);
199         menuBar->setBackgroundRole(QPalette::Window);
200         menuBar->setVisible(false);
201         layout->addWidget(menuBar, 0);
202 
203         toolBar = new KToolBar(p);
204         toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
205         toolBar->setBackgroundRole(QPalette::Window);
206         toolBar->setVisible(false);
207         layout->addWidget(toolBar, 0);
208 
209         /// main part of the widget
210 
211         stackedWidget = new QStackedWidget(p);
212         layout->addWidget(stackedWidget, 1);
213 
214         /// default widget if no preview is available
215         message = new ImageLabel(i18n("No preview available"), stackedWidget);
216         message->setAlignment(Qt::AlignCenter);
217         message->setWordWrap(true);
218         swpMessage = stackedWidget->addWidget(message);
219         connect(message, &QLabel::linkActivated, p, &DocumentPreview::linkActivated);
220 
221         /// add parts to stackedWidget
222         okularPart = locatePart(QStringLiteral("application/pdf"), stackedWidget);
223         swpOkular = (okularPart == nullptr) ? -1 : stackedWidget->addWidget(okularPart->widget());
224         if (okularPart == nullptr || swpOkular < 0) {
225             qCWarning(LOG_KBIBTEX_PROGRAM) << "No 'KDE Framworks 5'-based Okular part for PDF or PostScript document preview available.";
226         }
227 #ifdef HAVE_WEBENGINEWIDGETS
228         qCDebug(LOG_KBIBTEX_PROGRAM) << "WebEngine is available, using it instead of WebKit or HTML KPart (both neither considered nor tested for) for HTML/Web preview.";
229         /// To make DrKonqi handle crashes in Chromium-based QtWebEngine,
230         /// set a certain environment variable. For details, see here:
231         /// https://www.dvratil.cz/2018/10/drkonqi-and-qtwebengine/
232         /// https://phabricator.kde.org/D16004
233         const auto chromiumFlags = qgetenv("QTWEBENGINE_CHROMIUM_FLAGS");
234         if (!chromiumFlags.contains("disable-in-process-stack-traces")) {
235             qputenv("QTWEBENGINE_CHROMIUM_FLAGS", chromiumFlags + " --disable-in-process-stack-traces");
236         }
237         htmlWidget = new QWebEngineView(stackedWidget);
238         swpHTML = stackedWidget->addWidget(htmlWidget);
239         connect(htmlWidget, &QWebEngineView::loadFinished, p, &DocumentPreview::loadingFinished);
240 #else // HAVE_WEBENGINEWIDGETS
241 #ifdef HAVE_WEBKITWIDGETS
242         qCDebug(LOG_KBIBTEX_PROGRAM) << "WebKit is available, using it instead of WebEngine (missing) or HTML KPart (not considered) for HTML/Web preview.";
243         htmlWidget = new QWebView(stackedWidget);
244         swpHTML = stackedWidget->addWidget(htmlWidget);
245         connect(htmlWidget, &QWebView::loadFinished, p, &DocumentPreview::loadingFinished);
246 #else // HAVE_WEBKITWIDGETS
247         htmlPart = locatePart(QStringLiteral("text/html"), stackedWidget);
248         if (htmlPart != nullptr) {
249             qCDebug(LOG_KBIBTEX_PROGRAM) << "HTML KPart is available, using it instead of WebEngine or WebKit (neither available) for HTML/Web preview.";
250             swpHTML = stackedWidget->addWidget(htmlPart->widget());
251         } else {
252             qCDebug(LOG_KBIBTEX_PROGRAM) << "No HTML viewing component is available, disabling HTML/Web preview.";
253             swpHTML = -1;
254         }
255 #endif // HAVE_WEBKITWIDGETS
256 #endif // HAVE_WEBENGINEWIDGETS
257 
258         loadState();
259 
260         connect(externalViewerButton, &QPushButton::clicked, p, &DocumentPreview::openExternally);
261         connect(urlComboBox, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated), p, &DocumentPreview::comboBoxChanged);
262         connect(onlyLocalFilesButton, &QPushButton::toggled, p, &DocumentPreview::onlyLocalFilesChanged);
263     }
264 
addUrl(const struct UrlInfo & urlInfo)265     bool addUrl(const struct UrlInfo &urlInfo) {
266         bool isLocal = KBibTeX::isLocalOrRelative(urlInfo.url);
267         anyLocal |= isLocal;
268 
269         if (!onlyLocalFilesButton->isChecked() && !isLocal) return true; ///< ignore URL if only local files are allowed
270 
271         if (isLocal) {
272             /// create a drop-down list entry if file is a local file
273             /// (based on patch by Luis Silva)
274             QString fn = urlInfo.url.fileName();
275             QString full = urlInfo.url.url(QUrl::PreferLocalFile);
276             QString dir = urlInfo.url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path();
277             QString text = fn.isEmpty() ? full : (dir.isEmpty() ? fn : QString(QStringLiteral("%1 [%2]")).arg(fn, dir));
278             urlComboBox->addItem(urlInfo.icon, text);
279         } else {
280             /// create a drop-down list entry if file is a remote file
281             urlComboBox->addItem(urlInfo.icon, urlInfo.url.toDisplayString());
282         }
283         urlComboBox->setEnabled(true);
284         cbxEntryToUrlInfo.insert(urlComboBox->count() - 1, urlInfo);
285 
286         externalViewerButton->setEnabled(true);
287         if (urlComboBox->count() == 1 || ///< first entry in combobox
288                 isLocal || ///< local files always preferred over URLs
289                 /// prefer arXiv summary URLs over other URLs
290                 (!anyLocal && urlInfo.url.host().contains(QStringLiteral("arxiv.org/abs")))) {
291             showUrl(urlInfo);
292         }
293 
294         return true;
295     }
296 
update()297     void update() {
298         p->setCursor(Qt::WaitCursor);
299 
300         /// reset and clear all controls
301         if (swpOkular >= 0 && okularPart != nullptr)
302             okularPart->closeUrl();
303 #ifdef HAVE_WEBENGINEWIDGETS
304         htmlWidget->stop();
305 #else // HAVE_WEBENGINEWIDGETS
306 #ifdef HAVE_WEBKITWIDGETS
307         htmlWidget->stop();
308 #else // HAVE_WEBKITWIDGETS
309         if (swpHTML >= 0 && htmlPart != nullptr)
310             htmlPart->closeUrl();
311 #endif // HAVE_WEBKITWIDGETS
312 #endif // HAVE_WEBENGINEWIDGETS
313         urlComboBox->setEnabled(false);
314         urlComboBox->clear();
315         cbxEntryToUrlInfo.clear();
316         externalViewerButton->setEnabled(false);
317         showMessage(i18n("Refreshing...")); // krazy:exclude=qmethods
318 
319         /// cancel/kill all running jobs
320         auto it = runningJobs.begin();
321         while (it != runningJobs.end()) {
322             (*it)->kill();
323             it = runningJobs.erase(it);
324         }
325 
326         /// clear flag that memorizes if any local file was referenced
327         anyLocal = false;
328         anyRemote = false;
329 
330         /// do not load external reference if widget is hidden
331         if (isVisible()) {
332             const auto urlList = FileInfo::entryUrls(entry, baseUrl, FileInfo::TestExistenceYes);
333             for (const QUrl &url : urlList) {
334                 bool isLocal = KBibTeX::isLocalOrRelative(url);
335                 anyRemote |= !isLocal;
336                 if (!onlyLocalFilesButton->isChecked() && !isLocal) continue;
337 
338                 KIO::StatJob *job = KIO::stat(url, KIO::StatJob::SourceSide, 3, KIO::HideProgressInfo);
339                 runningJobs << job;
340                 KJobWidgets::setWindow(job, p);
341                 connect(job, &KIO::StatJob::result, p, &DocumentPreview::statFinished);
342             }
343             if (urlList.isEmpty()) {
344                 /// Case no URLs associated with this entry.
345                 /// For-loop above was never executed.
346                 showMessage(i18n("No documents to show.")); // krazy:exclude=qmethods
347                 p->setCursor(Qt::ArrowCursor);
348             } else if (runningJobs.isEmpty()) {
349                 /// Case no stat jobs are running. As there were URLs (tested in
350                 /// previous condition), this implies that there were remote
351                 /// references that were ignored by executing "continue" above.
352                 /// Give user hint that by enabling remote files, more can be shown.
353                 showMessage(i18n("<qt>No documents to show.<br/><a href=\"disableonlylocalfiles\">Disable the restriction</a> to local files to see remote documents.</qt>")); // krazy:exclude=qmethods
354                 p->setCursor(Qt::ArrowCursor);
355             }
356         } else
357             p->setCursor(Qt::ArrowCursor);
358     }
359 
showMessage(const QString & msgText)360     void showMessage(const QString &msgText) {
361         stackedWidget->setCurrentIndex(swpMessage);
362         message->setPixmap(QPixmap());
363         message->setText(msgText);
364         if (swpOkular >= 0)
365             stackedWidget->widget(swpOkular)->setEnabled(false);
366         if (swpHTML >= 0)
367             stackedWidget->widget(swpHTML)->setEnabled(false);
368         menuBar->setVisible(false);
369         toolBar->setVisible(true);
370         menuBar->clear();
371         toolBar->clear();
372     }
373 
374 
setupToolMenuBarForPart(const KParts::ReadOnlyPart * part)375     void setupToolMenuBarForPart(const KParts::ReadOnlyPart *part) {
376         /*
377         KAction *printAction = KStandardAction::print(part, SLOT(slotPrint()), part->actionCollection());
378         printAction->setEnabled(false);
379         connect(part, SIGNAL(enablePrintAction(bool)), printAction, SLOT(setEnabled(bool)));
380         */
381 
382         QDomDocument doc = part->domDocument();
383         QDomElement docElem = doc.documentElement();
384 
385         QDomNodeList toolbarNodes = docElem.elementsByTagName(QStringLiteral("ToolBar"));
386         for (int i = 0; i < toolbarNodes.count(); ++i) {
387             QDomNodeList toolbarItems = toolbarNodes.at(i).childNodes();
388             for (int j = 0; j < toolbarItems.count(); ++j) {
389                 QDomNode toolbarItem = toolbarItems.at(j);
390                 if (toolbarItem.nodeName() == QStringLiteral("Action")) {
391                     QString actionName = toolbarItem.attributes().namedItem(QStringLiteral("name")).nodeValue();
392                     toolBar->addAction(part->actionCollection()->action(actionName));
393                 } else if (toolbarItem.nodeName() == QStringLiteral("Separator")) {
394                     toolBar->addSeparator();
395                 }
396             }
397         }
398 
399 
400         QDomNodeList menubarNodes = docElem.elementsByTagName(QStringLiteral("MenuBar"));
401         for (int i = 0; i < menubarNodes.count(); ++i) {
402             QDomNodeList menubarNode = menubarNodes.at(i).childNodes();
403             for (int j = 0; j < menubarNode.count(); ++j) {
404                 QDomNode menubarItem = menubarNode.at(j);
405                 if (menubarItem.nodeName() == QStringLiteral("Menu")) {
406                     QDomNodeList menuNode = menubarItem.childNodes();
407                     QString text;
408                     for (int k = 0; k < menuNode.count(); ++k) {
409                         QDomNode menuItem = menuNode.at(k);
410                         if (menuItem.nodeName() == QStringLiteral("text")) {
411                             text = menuItem.firstChild().toText().data();
412                             break;
413                         }
414                     }
415                     QMenu *menu = menuBar->addMenu(text);
416 
417                     for (int k = 0; k < menuNode.count(); ++k) {
418                         QDomNode menuItem = menuNode.at(k);
419                         if (menuItem.nodeName() == QStringLiteral("Action")) {
420                             QString actionName = menuItem.attributes().namedItem(QStringLiteral("name")).nodeValue();
421                             menu->addAction(part->actionCollection()->action(actionName));
422                         } else if (menuItem.nodeName() == QStringLiteral("Separator")) {
423                             menu->addSeparator();
424                         }
425                     }
426                 }
427             }
428         }
429 
430         QDomNodeList actionPropertiesList = docElem.elementsByTagName(QStringLiteral("ActionProperties"));
431         for (int i = 0; i < actionPropertiesList.count(); ++i) {
432             QDomNodeList actionProperties = actionPropertiesList.at(i).childNodes();
433             for (int j = 0; j < actionProperties.count(); ++j) {
434                 QDomNode actionNode = actionProperties.at(j);
435                 if (actionNode.nodeName() == QStringLiteral("Action")) {
436                     const QString actionName = actionNode.attributes().namedItem(QStringLiteral("name")).toAttr().nodeValue();
437                     const QString actionShortcut = actionNode.attributes().namedItem(QStringLiteral("shortcut")).toAttr().value();
438                     QAction *action = part->actionCollection()->action(actionName);
439                     if (action != nullptr) {
440                         action->setShortcut(QKeySequence(actionShortcut));
441                     }
442                 }
443             }
444         }
445 
446         menuBar->setVisible(true);
447         toolBar->setVisible(true);
448     }
449 
450 
showPart(const KParts::ReadOnlyPart * part,QWidget * widget)451     void showPart(const KParts::ReadOnlyPart *part, QWidget *widget) {
452         menuBar->setVisible(false);
453         toolBar->setVisible(false);
454         menuBar->clear();
455         toolBar->clear();
456 
457         if (okularPart != nullptr && part == okularPart && swpOkular >= 0) {
458             stackedWidget->setCurrentIndex(swpOkular);
459             stackedWidget->widget(swpOkular)->setEnabled(true);
460             setupToolMenuBarForPart(okularPart);
461 #ifdef HAVE_WEBENGINEWIDGETS
462         } else if (widget == htmlWidget) {
463             stackedWidget->setCurrentIndex(swpHTML);
464             stackedWidget->widget(swpHTML)->setEnabled(true);
465 #else // HAVE_WEBENGINEWIDGETS
466 #ifdef HAVE_WEBKITWIDGETS
467         } else if (widget == htmlWidget) {
468             stackedWidget->setCurrentIndex(swpHTML);
469             stackedWidget->widget(swpHTML)->setEnabled(true);
470 #else // HAVE_WEBKITWIDGETS
471         } else if (htmlPart != nullptr && part == htmlPart && swpHTML >= 0) {
472             stackedWidget->setCurrentIndex(swpHTML);
473             stackedWidget->widget(swpHTML)->setEnabled(true);
474             setupToolMenuBarForPart(htmlPart);
475 #endif // HAVE_WEBKITWIDGETS
476 #endif // HAVE_WEBENGINEWIDGETS
477         } else if (widget == message) {
478             stackedWidget->setCurrentIndex(swpMessage);
479         } else
480             showMessage(i18n("Cannot show requested part")); // krazy:exclude=qmethods
481     }
482 
showUrl(const struct UrlInfo & urlInfo)483     bool showUrl(const struct UrlInfo &urlInfo) {
484         static const QStringList okularMimetypes {QStringLiteral("application/x-pdf"), QStringLiteral("application/pdf"), QStringLiteral("application/x-gzpdf"), QStringLiteral("application/x-bzpdf"), QStringLiteral("application/x-wwf"), QStringLiteral("image/vnd.djvu"), QStringLiteral("image/vnd.djvu+multipage"), QStringLiteral("application/postscript"), QStringLiteral("image/x-eps"), QStringLiteral("application/x-gzpostscript"), QStringLiteral("application/x-bzpostscript"), QStringLiteral("image/x-gzeps"), QStringLiteral("image/x-bzeps")};
485         static const QStringList htmlMimetypes {QStringLiteral("text/html"), QStringLiteral("application/xml"), QStringLiteral("application/xhtml+xml")};
486         static const QStringList imageMimetypes {QStringLiteral("image/jpeg"), QStringLiteral("image/png"), QStringLiteral("image/gif"), QStringLiteral("image/tiff")};
487 
488         if (swpHTML >= 0)
489             stackedWidget->widget(swpHTML)->setEnabled(false);
490         if (swpOkular >= 0 && okularPart != nullptr) {
491             stackedWidget->widget(swpOkular)->setEnabled(false);
492             okularPart->closeUrl();
493         }
494 #ifdef HAVE_WEBENGINEWIDGETS
495         htmlWidget->stop();
496 #else // HAVE_WEBENGINEWIDGETS
497 #ifdef HAVE_WEBKITWIDGETS
498         htmlWidget->stop();
499 #else // HAVE_WEBKITWIDGETS
500         if (swpHTML >= 0 && htmlPart != nullptr)
501             htmlPart->closeUrl();
502 #endif // HAVE_WEBKITWIDGETS
503 #endif // HAVE_WEBENGINEWIDGETS
504 
505         if (swpOkular >= 0 && okularPart != nullptr && okularMimetypes.contains(urlInfo.mimeType)) {
506             p->setCursor(Qt::BusyCursor);
507             showMessage(i18n("Loading...")); // krazy:exclude=qmethods
508             return okularPart->openUrl(urlInfo.url);
509         } else if (htmlMimetypes.contains(urlInfo.mimeType)) {
510             p->setCursor(Qt::BusyCursor);
511             showMessage(i18n("Loading...")); // krazy:exclude=qmethods
512 #ifdef HAVE_WEBENGINEWIDGETS
513             htmlWidget->load(urlInfo.url);
514             return true;
515 #else // HAVE_WEBENGINEWIDGETS
516 #ifdef HAVE_WEBKITWIDGETS
517             htmlWidget->load(urlInfo.url);
518             return true;
519 #else // HAVE_WEBKITWIDGETS
520             return (swpHTML >= 0 && htmlPart != nullptr) ? htmlPart->openUrl(urlInfo.url) : false;
521 #endif // HAVE_WEBKITWIDGETS
522 #endif // HAVE_WEBENGINEWIDGETS
523         } else if (imageMimetypes.contains(urlInfo.mimeType)) {
524             p->setCursor(Qt::BusyCursor);
525             message->setPixmap(QPixmap(urlInfo.url.url(QUrl::PreferLocalFile)));
526             showPart(nullptr, message);
527             p->unsetCursor();
528             return true;
529         } else {
530             QString additionalInformation;
531             if (urlInfo.mimeType == QStringLiteral("application/pdf"))
532                 additionalInformation = i18nc("Additional information in case there is not KPart available for mime type 'application/pdf'", "<br/><br/>Please install <a href=\"https://userbase.kde.org/Okular\">Okular</a> for KDE Frameworks&nbsp;5 to make use of its PDF viewing component.<br/>Okular for KDE&nbsp;4 will not work.");
533             showMessage(i18nc("First parameter is mime type, second parameter is optional information (may be empty)", "<qt>Don't know how to show mimetype '%1'.%2</qt>", urlInfo.mimeType, additionalInformation)); // krazy:exclude=qmethods
534         }
535 
536         return false;
537     }
538 
openExternally()539     void openExternally() {
540         QUrl url(cbxEntryToUrlInfo[urlComboBox->currentIndex()].url);
541         /// Guess mime type for url to open
542         QMimeType mimeType = FileInfo::mimeTypeForUrl(url);
543         const QString mimeTypeName = mimeType.name();
544         /// Ask KDE subsystem to open url in viewer matching mime type
545 #if KIO_VERSION < 0x051f00 // < 5.31.0
546         KRun::runUrl(url, mimeTypeName, p, false, false);
547 #else // KIO_VERSION < 0x051f00 // >= 5.31.0
548         KRun::runUrl(url, mimeTypeName, p, KRun::RunFlags());
549 #endif // KIO_VERSION < 0x051f00
550     }
551 
urlMetaInfo(const QUrl & url)552     UrlInfo urlMetaInfo(const QUrl &url) {
553         UrlInfo result;
554         result.url = url;
555 
556         if (!KBibTeX::isLocalOrRelative(url) && url.fileName().isEmpty()) {
557             /// URLs not pointing to a specific file should be opened with a web browser component
558             result.icon = QIcon::fromTheme(QStringLiteral("text-html"));
559             result.mimeType = QStringLiteral("text/html");
560             return result;
561         }
562 
563         QMimeType mimeType = FileInfo::mimeTypeForUrl(url);
564         // FIXME accuracy, necessary:
565         /*
566         if (accuracy < 50) {
567         QMimeDatabase db;
568             mimeType = db.mimeTypeForFile(url.fileName());
569         }
570         */
571         result.mimeType = mimeType.name();
572         result.icon = QIcon::fromTheme(mimeType.iconName());
573 
574         if (result.mimeType == QStringLiteral("application/octet-stream")) {
575             /// application/octet-stream is a fall-back if KDE did not know better
576             result.icon = QIcon::fromTheme(QStringLiteral("text-html"));
577             result.mimeType = QStringLiteral("text/html");
578         } else if ((result.mimeType.isEmpty() || result.mimeType == QStringLiteral("inode/directory")) && (result.url.scheme() == QStringLiteral("http") || result.url.scheme() == QStringLiteral("https"))) {
579             /// directory via http means normal webpage (not browsable directory)
580             result.icon = QIcon::fromTheme(QStringLiteral("text-html"));
581             result.mimeType = QStringLiteral("text/html");
582         }
583 
584         if (url.url(QUrl::PreferLocalFile).startsWith(arXivPDFUrlStart)) {
585             result.icon = QIcon::fromTheme(QStringLiteral("application-pdf"));
586             result.mimeType = QStringLiteral("application/pdf");
587         }
588 
589         return result;
590     }
591 
comboBoxChanged(int index)592     void comboBoxChanged(int index) {
593         showUrl(cbxEntryToUrlInfo[index]);
594     }
595 
isVisible()596     bool isVisible() {
597         /// get dock where this widget is inside
598         /// static cast is save as constructor requires parent to be QDockWidget
599         QDockWidget *pp = static_cast<QDockWidget *>(p->parent());
600         return pp != nullptr && !pp->isHidden();
601     }
602 
loadState()603     void loadState() {
604         KConfigGroup configGroup(config, configGroupName);
605         onlyLocalFilesButton->setChecked(!configGroup.readEntry(onlyLocalFilesCheckConfig, true));
606     }
607 
saveState()608     void saveState() {
609         KConfigGroup configGroup(config, configGroupName);
610         configGroup.writeEntry(onlyLocalFilesCheckConfig, !onlyLocalFilesButton->isChecked());
611         config->sync();
612     }
613 };
614 
615 const QString DocumentPreview::DocumentPreviewPrivate::arXivPDFUrlStart = QStringLiteral("http://arxiv.org/pdf/");
616 const QString DocumentPreview::DocumentPreviewPrivate::configGroupName = QStringLiteral("URL Preview");
617 const QString DocumentPreview::DocumentPreviewPrivate::onlyLocalFilesCheckConfig = QStringLiteral("OnlyLocalFiles");
618 
DocumentPreview(QDockWidget * parent)619 DocumentPreview::DocumentPreview(QDockWidget *parent)
620         : QWidget(parent), d(new DocumentPreviewPrivate(this))
621 {
622     connect(parent, &QDockWidget::visibilityChanged, this, &DocumentPreview::visibilityChanged);
623 }
624 
~DocumentPreview()625 DocumentPreview::~DocumentPreview()
626 {
627     delete d;
628 }
629 
setElement(QSharedPointer<Element> element,const File *)630 void DocumentPreview::setElement(QSharedPointer<Element> element, const File *)
631 {
632     d->entry = element.dynamicCast<const Entry>();
633     d->update();
634 }
635 
openExternally()636 void DocumentPreview::openExternally()
637 {
638     d->openExternally();
639 }
640 
setBibTeXUrl(const QUrl & url)641 void DocumentPreview::setBibTeXUrl(const QUrl &url)
642 {
643     d->baseUrl = url;
644 }
645 
onlyLocalFilesChanged()646 void DocumentPreview::onlyLocalFilesChanged()
647 {
648     d->saveState();
649     d->update();
650 }
651 
visibilityChanged(bool)652 void DocumentPreview::visibilityChanged(bool)
653 {
654     d->update();
655 }
656 
comboBoxChanged(int index)657 void DocumentPreview::comboBoxChanged(int index)
658 {
659     d->comboBoxChanged(index);
660 }
661 
statFinished(KJob * kjob)662 void DocumentPreview::statFinished(KJob *kjob)
663 {
664     KIO::StatJob *job = static_cast<KIO::StatJob *>(kjob);
665     d->runningJobs.removeOne(job);
666     if (!job->error()) {
667         const QUrl url = job->mostLocalUrl();
668         DocumentPreviewPrivate::UrlInfo urlInfo = d->urlMetaInfo(url);
669         setCursor(d->runningJobs.isEmpty() ? Qt::ArrowCursor : Qt::BusyCursor);
670         d->addUrl(urlInfo);
671     } else {
672         qCWarning(LOG_KBIBTEX_PROGRAM) << job->error() << job->errorString();
673     }
674 
675     if (d->runningJobs.isEmpty()) {
676         /// If this was the last background stat job ...
677         setCursor(Qt::ArrowCursor);
678 
679         if (d->urlComboBox->count() < 1) {
680             /// In case that no valid references were found by the stat jobs ...
681             if (d->anyRemote && !d->onlyLocalFilesButton->isChecked()) {
682                 /// There are some remote URLs to probe,
683                 /// but user was only looking for local files
684                 d->showMessage(i18n("<qt>No documents to show.<br/><a href=\"disableonlylocalfiles\">Disable the restriction</a> to local files to see remote documents.</qt>")); // krazy:exclude=qmethods
685             } else {
686                 /// No stat job at all succeeded. Show message to user.
687                 d->showMessage(i18n("No documents to show.\nSome URLs or files could not be retrieved.")); // krazy:exclude=qmethods
688             }
689         }
690     }
691 }
692 
loadingFinished()693 void DocumentPreview::loadingFinished()
694 {
695     setCursor(Qt::ArrowCursor);
696     d->showPart(qobject_cast<KParts::ReadOnlyPart *>(sender()), qobject_cast<QWidget *>(sender()));
697 }
698 
linkActivated(const QString & link)699 void DocumentPreview::linkActivated(const QString &link)
700 {
701     if (link == QStringLiteral("disableonlylocalfiles"))
702         d->onlyLocalFilesButton->setChecked(true);
703     else if (link.startsWith(QStringLiteral("http://")) || link.startsWith(QStringLiteral("https://"))) {
704         const QUrl urlToOpen = QUrl::fromUserInput(link);
705         if (urlToOpen.isValid()) {
706             /// Guess mime type for url to open
707             QMimeType mimeType = FileInfo::mimeTypeForUrl(urlToOpen);
708             const QString mimeTypeName = mimeType.name();
709             /// Ask KDE subsystem to open url in viewer matching mime type
710 #if KIO_VERSION < 0x051f00 // < 5.31.0
711             KRun::runUrl(urlToOpen, mimeTypeName, this, false, false);
712 #else // KIO_VERSION < 0x051f00 // >= 5.31.0
713             KRun::runUrl(urlToOpen, mimeTypeName, this, KRun::RunFlags());
714 #endif // KIO_VERSION < 0x051f00
715         }
716     }
717 }
718