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 5 to make use of its PDF viewing component.<br/>Okular for KDE 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