diff --git a/addons/preview/previewwidget.h b/addons/preview/previewwidget.h --- a/addons/preview/previewwidget.h +++ b/addons/preview/previewwidget.h @@ -23,11 +23,13 @@ // KF #include // Qt +#include #include class KTextEditorPreviewPlugin; namespace KTextEditor { +class Document; class MainWindow; class View; } @@ -94,13 +96,15 @@ * @param view the view or, if there is none, a nullptr */ void setTextEditorView(KTextEditor::View* view); + void resetTextEditorView(KTextEditor::Document* document); + void unsetDocument(KTextEditor::Document* document); private: void toggleDocumentLocking(bool locked); - void handleLockedDocumentClosing(); void toggleAutoUpdating(bool autoRefreshing); void updatePreview(); void showAboutKPartPlugin(); + void clearMenu(); private: KToggleAction* m_lockAction; @@ -113,9 +117,10 @@ KTextEditorPreviewPlugin* const m_core; KTextEditor::MainWindow* const m_mainWindow; + KTextEditor::Document* m_previewedTextEditorDocument = nullptr; KTextEditor::View* m_previewedTextEditorView = nullptr; QString m_currentServiceId; - KPartView* m_partView = nullptr; + QPointer m_partView; KXMLGUIFactory* m_xmlGuiFactory; }; diff --git a/addons/preview/previewwidget.cpp b/addons/preview/previewwidget.cpp --- a/addons/preview/previewwidget.cpp +++ b/addons/preview/previewwidget.cpp @@ -38,6 +38,7 @@ #include #include #include +#include // Qt #include @@ -131,139 +132,166 @@ void PreviewWidget::setTextEditorView(KTextEditor::View* view) { - if ((m_previewedTextEditorView == view) || - !isVisible() || - m_lockAction->isChecked()) { + if ((view && view == m_previewedTextEditorView && + view->document() == m_previewedTextEditorDocument) || + !view || !isVisible() || m_lockAction->isChecked()) { return; } m_previewedTextEditorView = view; + m_previewedTextEditorDocument = view ? view->document() : nullptr; - KService::Ptr service; - if (m_previewedTextEditorView) { - // TODO: mimetype is not set for new document which have not been saved yet. - // needs another way to get this info, or perhaps some proper fix in Kate/Kdevelop - // to guess the mimetype based on current content, selected mode/highlighting etc. - // which then also would needs a signal mimetypeChanged and handling here - const auto mimeType = m_previewedTextEditorView->document()->mimeType(); - service = KMimeTypeTrader::self()->preferredService(mimeType, QStringLiteral("KParts/ReadOnlyPart")); - if (service) { - qCDebug(KTEPREVIEW) << "Found preferred kpart service named" << service->name() - << "with library" <library() - << "for mimetype" << mimeType; + resetTextEditorView(m_previewedTextEditorDocument); +} - if (service->library().isEmpty()) { - qCWarning(KTEPREVIEW) << "Discarding preferred kpart service due to empty library name:" << service->name(); - service.reset(); - } +void PreviewWidget::resetTextEditorView(KTextEditor::Document* document) { + if (!isVisible() || m_previewedTextEditorDocument != document) { + return; + } + + KService::Ptr service; - // no interest in kparts which also just display the text (like katepart itself) - // TODO: what about parts which also support importing plain text and turning into richer format - // and thus have it in their mimetypes list? - // could that perhaps be solved by introducing the concept of "native" and "imported" mimetypes? - // or making a distinction between source editors/viewers and final editors/viewers? - // latter would also help other source editors/viewers like a hexeditor, which "supports" any mimetype - if (service && service->mimeTypes().contains(QStringLiteral("text/plain"))) { - qCDebug(KTEPREVIEW) << "Blindly discarding preferred service as it also supports text/plain, to avoid useless plain/text preview."; - service.reset(); + if (m_previewedTextEditorDocument) { + // TODO: mimetype is not set for new documents which have not been saved yet. + // Maybe retry to guess as soon as content is inserted. + + // Get mimetypes assigned to the currently set mode. + auto mimeTypes = KConfigGroup(KSharedConfig::openConfig(QStringLiteral("katemoderc")), m_previewedTextEditorDocument->mode()).readXdgListEntry("Mimetypes"); + // Also try to guess from the content, if the above fails. + mimeTypes << m_previewedTextEditorDocument->mimeType(); + + foreach (const auto mimeType, mimeTypes) { + service = KMimeTypeTrader::self()->preferredService(mimeType, QStringLiteral("KParts/ReadOnlyPart")); + if (service) { + qCDebug(KTEPREVIEW) << "Found preferred kpart service named" << service->name() + << "with library" <library() + << "for mimetype" << mimeType; + + if (service->library().isEmpty()) { + qCWarning(KTEPREVIEW) << "Discarding preferred kpart service due to empty library name:" << service->name(); + service.reset(); + } + + // no interest in kparts which also just display the text (like katepart itself) + // TODO: what about parts which also support importing plain text and turning into richer format + // and thus have it in their mimetypes list? + // could that perhaps be solved by introducing the concept of "native" and "imported" mimetypes? + // or making a distinction between source editors/viewers and final editors/viewers? + // latter would also help other source editors/viewers like a hexeditor, which "supports" any mimetype + if (service && service->mimeTypes().contains(QStringLiteral("text/plain"))) { + qCDebug(KTEPREVIEW) << "Blindly discarding preferred service as it also supports text/plain, to avoid useless plain/text preview."; + service.reset(); + } + + if (service) { + break; + } } - } else { - qCDebug(KTEPREVIEW) << "Found no preferred kpart service for mimetype" << mimeType; } + if (!service) { + qCDebug(KTEPREVIEW) << "Found no preferred kpart service for mimetypes" << mimeTypes; + } + + // Update if the mode is changed. The signal may also be emitted, when a new + // url is loaded, therefore wait (QueuedConnection) for the document to load. + connect(m_previewedTextEditorDocument, &KTextEditor::Document::modeChanged, + this, &PreviewWidget::resetTextEditorView, Qt::QueuedConnection); + // Explicitly clear the old document, which otherwise might be accessed in + // m_partView->setDocument. + connect(m_previewedTextEditorDocument, &KTextEditor::Document::aboutToClose, + this, &PreviewWidget::unsetDocument); } // change of preview type? // TODO: find a better id than library? const QString serviceId = service ? service->library() : QString(); if (serviceId != m_currentServiceId) { if (m_partView) { - // clear kpart menu - m_xmlGuiFactory->removeClient(m_partView->kPart()); - m_kPartMenu->clear(); - - removeWidget(m_partView->widget()); - delete m_partView; + clearMenu(); } m_currentServiceId = serviceId; if (service) { qCDebug(KTEPREVIEW) << "Creating new kpart service instance."; m_partView = new KPartView(service, this); - m_partView->setAutoUpdating(m_autoUpdateAction->isChecked()); + const bool autoupdate = m_autoUpdateAction->isChecked(); + m_partView->setAutoUpdating(autoupdate); int index = addWidget(m_partView->widget()); setCurrentIndex(index); // update kpart menu const auto kPart = m_partView->kPart(); if (kPart) { + m_xmlGuiFactory->addClient(kPart); + const auto kPartDisplayName = kPart->componentData().displayName(); m_aboutKPartAction->setText(i18n("About %1", kPartDisplayName)); - m_xmlGuiFactory->addClient(kPart); + m_aboutKPartAction->setEnabled(true); m_kPartMenu->addSeparator(); m_kPartMenu->addAction(m_aboutKPartAction); + m_kPartMenuAction->setEnabled(true); } + + m_updateAction->setEnabled(!autoupdate); } else { m_partView = nullptr; } - } else { - if (m_partView) { - qCDebug(KTEPREVIEW) << "Reusing active kpart service instance."; - } + } else if (m_partView) { + qCDebug(KTEPREVIEW) << "Reusing active kpart service instance."; } if (m_partView) { - m_partView->setDocument(m_previewedTextEditorView->document()); + m_partView->setDocument(m_previewedTextEditorDocument); } +} - m_updateAction->setEnabled(m_partView && !m_autoUpdateAction->isChecked()); - const bool hasKPart = (m_partView && m_partView->kPart()); - m_kPartMenuAction->setEnabled(hasKPart); - m_aboutKPartAction->setEnabled(hasKPart); +void PreviewWidget::unsetDocument(KTextEditor::Document* document) +{ + if (!m_partView || m_previewedTextEditorDocument != document) { + return; + } + + m_partView->setDocument(nullptr); + m_previewedTextEditorDocument = nullptr; + + // remove any current partview + clearMenu(); + m_partView = nullptr; + + m_currentServiceId.clear(); } void PreviewWidget::showEvent(QShowEvent* event) { Q_UNUSED(event); m_updateAction->setEnabled(m_partView && !m_autoUpdateAction->isChecked()); - setTextEditorView(m_mainWindow->activeView()); + if (m_lockAction->isChecked()) { + resetTextEditorView(m_previewedTextEditorDocument); + } else { + setTextEditorView(m_mainWindow->activeView()); + } } void PreviewWidget::hideEvent(QHideEvent* event) { Q_UNUSED(event); // keep active part for reuse, but close preview document - if (m_partView) { - // TODO: we also get hide event in kdevelop when the view is changed, - // need to find out how to filter this out or how to fix kdevelop - // so currently keep the preview document -// m_partView->setDocument(nullptr); - } + // TODO: we also get hide event in kdevelop when the view is changed, + // need to find out how to filter this out or how to fix kdevelop + // so currently keep the preview document +// unsetDocument(m_previewedTextEditorDocument); m_updateAction->setEnabled(false); } void PreviewWidget::toggleDocumentLocking(bool locked) { - if (locked) { - if (!m_partView) { - // nothing to do - return; - } - auto document = m_partView->document(); - connect(document, &KTextEditor::Document::aboutToClose, - this, &PreviewWidget::handleLockedDocumentClosing); - } else { - if (m_partView) { - auto document = m_partView->document(); - disconnect(document, &KTextEditor::Document::aboutToClose, - this, &PreviewWidget::handleLockedDocumentClosing); - } - // jump tp current view + if (!locked) { setTextEditorView(m_mainWindow->activeView()); } } @@ -281,19 +309,9 @@ void PreviewWidget::updatePreview() { - m_partView->updatePreview(); -} - -void PreviewWidget::handleLockedDocumentClosing() -{ - // remove any current partview - if (m_partView) { - removeWidget(m_partView->widget()); - delete m_partView; - m_partView = nullptr; + if (m_partView && m_partView->document()) { + m_partView->updatePreview(); } - - m_currentServiceId.clear(); } QWidget* PreviewWidget::createContainer(QWidget* parent, int index, const QDomElement& element, QAction*& containerAction) @@ -338,3 +356,17 @@ delete aboutDialog; } } + +void PreviewWidget::clearMenu() +{ + // clear kpart menu + m_xmlGuiFactory->removeClient(m_partView->kPart()); + m_kPartMenu->clear(); + + removeWidget(m_partView->widget()); + delete m_partView; + + m_updateAction->setEnabled(false); + m_kPartMenuAction->setEnabled(false); + m_aboutKPartAction->setEnabled(false); +}