diff --git a/src/browserextension.cpp b/src/browserextension.cpp index 56512d1..481cba0 100644 --- a/src/browserextension.cpp +++ b/src/browserextension.cpp @@ -1,336 +1,336 @@ /* This file is part of the KDE project Copyright (C) 1999 Simon Hausmann (C) 1999 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "browserextension.h" +#include "kparts_logging.h" + #include #include #include #include #include #include #include #include -#include - using namespace KParts; namespace KParts { // Internal class, use to store the status of the actions class KBitArray { public: int val; KBitArray() { val = 0; } bool operator [](int index) { return (val & (1 << index)) ? true : false; } void setBit(int index, bool value) { if (value) { val = val | (1 << index); } else { val = val & ~(1 << index); } } }; class Q_DECL_HIDDEN BrowserExtension::BrowserExtensionPrivate { public: BrowserExtensionPrivate(KParts::ReadOnlyPart *parent) : m_urlDropHandlingEnabled(false), m_browserInterface(nullptr), m_part(parent) {} struct DelayedRequest { QUrl m_delayedURL; KParts::OpenUrlArguments m_delayedArgs; KParts::BrowserArguments m_delayedBrowserArgs; }; QList m_requests; bool m_urlDropHandlingEnabled; KBitArray m_actionStatus; QMap m_actionText; BrowserInterface *m_browserInterface; static void createActionSlotMap(); KParts::ReadOnlyPart *m_part; OpenUrlArguments m_args; BrowserArguments m_browserArgs; }; Q_GLOBAL_STATIC(BrowserExtension::ActionSlotMap, s_actionSlotMap) Q_GLOBAL_STATIC(BrowserExtension::ActionNumberMap, s_actionNumberMap) void BrowserExtension::BrowserExtensionPrivate::createActionSlotMap() { s_actionSlotMap()->insert("cut", SLOT(cut())); s_actionSlotMap()->insert("copy", SLOT(copy())); s_actionSlotMap()->insert("paste", SLOT(paste())); s_actionSlotMap()->insert("print", SLOT(print())); // Tricky. Those aren't actions in fact, but simply methods that a browserextension // can have or not. No need to return them here. //s_actionSlotMap()->insert( "reparseConfiguration", SLOT(reparseConfiguration()) ); //s_actionSlotMap()->insert( "refreshMimeTypes", SLOT(refreshMimeTypes()) ); // Create the action-number map ActionSlotMap::ConstIterator it = s_actionSlotMap()->constBegin(); ActionSlotMap::ConstIterator itEnd = s_actionSlotMap()->constEnd(); for (int i = 0; it != itEnd; ++it, ++i) { //qDebug() << " action " << it.key() << " number " << i; s_actionNumberMap()->insert(it.key(), i); } } } BrowserExtension::BrowserExtension(KParts::ReadOnlyPart *parent) : QObject(parent), d(new BrowserExtensionPrivate(parent)) { //qDebug() << "BrowserExtension::BrowserExtension() " << this; if (s_actionSlotMap()->isEmpty()) // Create the action-slot map { BrowserExtensionPrivate::createActionSlotMap(); } // Set the initial status of the actions depending on whether // they're supported or not const QMetaObject *metaobj = metaObject(); ActionSlotMap::ConstIterator it = s_actionSlotMap()->constBegin(); ActionSlotMap::ConstIterator itEnd = s_actionSlotMap()->constEnd(); for (int i = 0; it != itEnd; ++it, ++i) { // Does the extension have a slot with the name of this action ? QByteArray slotSig = it.key() + "()"; d->m_actionStatus.setBit(i, metaobj->indexOfMethod(slotSig.constData()) != -1); } connect(d->m_part, static_cast(&KParts::ReadOnlyPart::completed), this, &BrowserExtension::slotCompleted); connect(this, &BrowserExtension::openUrlRequest, this, &BrowserExtension::slotOpenUrlRequest); connect(this, &BrowserExtension::enableAction, this, &BrowserExtension::slotEnableAction); connect(this, &BrowserExtension::setActionText, this, &BrowserExtension::slotSetActionText); } BrowserExtension::~BrowserExtension() { //qDebug() << "BrowserExtension::~BrowserExtension() " << this; delete d; } void BrowserExtension::setBrowserArguments(const BrowserArguments &args) { d->m_browserArgs = args; } BrowserArguments BrowserExtension::browserArguments() const { return d->m_browserArgs; } int BrowserExtension::xOffset() { return 0; } int BrowserExtension::yOffset() { return 0; } void BrowserExtension::saveState(QDataStream &stream) { // TODO add d->m_part->mimeType() stream << d->m_part->url() << static_cast(xOffset()) << static_cast(yOffset()); } void BrowserExtension::restoreState(QDataStream &stream) { QUrl u; qint32 xOfs, yOfs; stream >> u >> xOfs >> yOfs; OpenUrlArguments args; args.setXOffset(xOfs); args.setYOffset(yOfs); // TODO add args.setMimeType d->m_part->setArguments(args); d->m_part->openUrl(u); } bool BrowserExtension::isURLDropHandlingEnabled() const { return d->m_urlDropHandlingEnabled; } void BrowserExtension::setURLDropHandlingEnabled(bool enable) { d->m_urlDropHandlingEnabled = enable; } void BrowserExtension::slotCompleted() { //empty the argument stuff, to avoid bogus/invalid values when opening a new url setBrowserArguments(BrowserArguments()); } void BrowserExtension::pasteRequest() { QString plain(QStringLiteral("plain")); QString url = QApplication::clipboard()->text(plain, QClipboard::Selection).trimmed(); // Remove linefeeds and any whitespace surrounding it. url.remove(QRegularExpression(QStringLiteral("[\\ ]*\\n+[\\ ]*"))); // Check if it's a URL QStringList filters = KUriFilter::self()->pluginNames(); filters.removeAll(QStringLiteral("kuriikwsfilter")); filters.removeAll(QStringLiteral("localdomainurifilter")); KUriFilterData filterData; filterData.setData(url); filterData.setCheckForExecutables(false); if (KUriFilter::self()->filterUri(filterData, filters)) { switch (filterData.uriType()) { case KUriFilterData::LocalFile: case KUriFilterData::LocalDir: case KUriFilterData::NetProtocol: slotOpenUrlRequest(filterData.uri()); break; case KUriFilterData::Error: KMessageBox::sorry(d->m_part->widget(), filterData.errorMsg()); break; default: break; } } else if (KUriFilter::self()->filterUri(filterData, QStringList(QStringLiteral("kuriikwsfilter"))) && url.length() < 250) { if (KMessageBox::questionYesNo(d->m_part->widget(), i18n("Do you want to search the Internet for %1?", url.toHtmlEscaped()), i18n("Internet Search"), KGuiItem(i18n("&Search"), QStringLiteral("edit-find")), KStandardGuiItem::cancel(), QStringLiteral("MiddleClickSearch")) == KMessageBox::Yes) { slotOpenUrlRequest(filterData.uri()); } } } void BrowserExtension::slotOpenUrlRequest(const QUrl &url, const KParts::OpenUrlArguments &args, const KParts::BrowserArguments &browserArgs) { //qDebug() << this << " BrowserExtension::slotOpenURLRequest(): url=" << url.url(); BrowserExtensionPrivate::DelayedRequest req; req.m_delayedURL = url; req.m_delayedArgs = args; req.m_delayedBrowserArgs = browserArgs; d->m_requests.append(req); QTimer::singleShot(0, this, &BrowserExtension::slotEmitOpenUrlRequestDelayed); } void BrowserExtension::slotEmitOpenUrlRequestDelayed() { if (d->m_requests.isEmpty()) { return; } BrowserExtensionPrivate::DelayedRequest req = d->m_requests.front(); d->m_requests.pop_front(); emit openUrlRequestDelayed(req.m_delayedURL, req.m_delayedArgs, req.m_delayedBrowserArgs); // tricky: do not do anything here! (no access to member variables, etc.) } void BrowserExtension::setBrowserInterface(BrowserInterface *impl) { d->m_browserInterface = impl; } BrowserInterface *BrowserExtension::browserInterface() const { return d->m_browserInterface; } void BrowserExtension::slotEnableAction(const char *name, bool enabled) { //qDebug() << "BrowserExtension::slotEnableAction " << name << " " << enabled; ActionNumberMap::ConstIterator it = s_actionNumberMap()->constFind(name); if (it != s_actionNumberMap()->constEnd()) { d->m_actionStatus.setBit(it.value(), enabled); //qDebug() << "BrowserExtension::slotEnableAction setting bit " << it.data() << " to " << enabled; } else { - qWarning() << "BrowserExtension::slotEnableAction unknown action " << name; + qCWarning(KPARTSLOG) << "BrowserExtension::slotEnableAction unknown action " << name; } } bool BrowserExtension::isActionEnabled(const char *name) const { int actionNumber = (*s_actionNumberMap())[ name ]; return d->m_actionStatus[ actionNumber ]; } void BrowserExtension::slotSetActionText(const char *name, const QString &text) { //qDebug() << "BrowserExtension::slotSetActionText " << name << " " << text; ActionNumberMap::ConstIterator it = s_actionNumberMap()->constFind(name); if (it != s_actionNumberMap()->constEnd()) { d->m_actionText[ it.value() ] = text; } else { - qWarning() << "BrowserExtension::slotSetActionText unknown action " << name; + qCWarning(KPARTSLOG) << "BrowserExtension::slotSetActionText unknown action " << name; } } QString BrowserExtension::actionText(const char *name) const { int actionNumber = (*s_actionNumberMap())[ name ]; QMap::ConstIterator it = d->m_actionText.constFind(actionNumber); if (it != d->m_actionText.constEnd()) { return *it; } return QString(); } // for compatibility BrowserExtension::ActionSlotMap BrowserExtension::actionSlotMap() { return *actionSlotMapPtr(); } BrowserExtension::ActionSlotMap *BrowserExtension::actionSlotMapPtr() { if (s_actionSlotMap()->isEmpty()) { BrowserExtensionPrivate::createActionSlotMap(); } return s_actionSlotMap(); } BrowserExtension *BrowserExtension::childObject(QObject *obj) { return obj->findChild(QString(), Qt::FindDirectChildrenOnly); } diff --git a/src/browseropenorsavequestion.cpp b/src/browseropenorsavequestion.cpp index 7e9c48e..b38dd69 100644 --- a/src/browseropenorsavequestion.cpp +++ b/src/browseropenorsavequestion.cpp @@ -1,365 +1,363 @@ /* Copyright (c) 2009, 2010 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "browseropenorsavequestion.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include - using namespace KParts; Q_DECLARE_METATYPE(KService::Ptr) class KParts::BrowserOpenOrSaveQuestionPrivate : public QDialog { Q_OBJECT public: enum { Save = QDialog::Accepted, OpenDefault = Save + 1, OpenWith = OpenDefault + 1, Cancel = QDialog::Rejected }; BrowserOpenOrSaveQuestionPrivate(QWidget *parent, const QUrl &url, const QString &mimeType) : QDialog(parent), url(url), mimeType(mimeType), features(BrowserOpenOrSaveQuestion::BasicFeatures) { const int spacingHint = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); // Use askSave or askEmbedOrSave from filetypesrc dontAskConfig = KSharedConfig::openConfig(QStringLiteral("filetypesrc"), KConfig::NoGlobals); setWindowTitle(url.host()); setObjectName(QStringLiteral("questionYesNoCancel")); QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->setSpacing(spacingHint * 2); // provide extra spacing QHBoxLayout *hLayout = new QHBoxLayout(); mainLayout->addLayout(hLayout, 5); QLabel *iconLabel = new QLabel(this); QStyleOption option; option.initFrom(this); QIcon icon = QIcon::fromTheme(QStringLiteral("dialog-information")); iconLabel->setPixmap(icon.pixmap(style()->pixelMetric(QStyle::PM_MessageBoxIconSize, &option, this))); hLayout->addWidget(iconLabel, 0, Qt::AlignCenter); hLayout->addSpacing(spacingHint); QVBoxLayout *textVLayout = new QVBoxLayout; questionLabel = new KSqueezedTextLabel(this); textVLayout->addWidget(questionLabel); fileNameLabel = new QLabel(this); fileNameLabel->hide(); textVLayout->addWidget(fileNameLabel); QMimeDatabase db; mime = db.mimeTypeForName(mimeType); QString mimeDescription(mimeType); if (mime.isValid()) { // Always prefer the mime-type comment over the raw type for display mimeDescription = (mime.comment().isEmpty() ? mime.name() : mime.comment()); } QLabel *mimeTypeLabel = new QLabel(this); mimeTypeLabel->setText(i18nc("@label Type of file", "Type: %1", mimeDescription)); mimeTypeLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); textVLayout->addWidget(mimeTypeLabel); hLayout->addLayout(textVLayout, 5); mainLayout->addStretch(15); dontAskAgainCheckBox = new QCheckBox(this); dontAskAgainCheckBox->setText(i18nc("@label:checkbox", "Remember action for files of this type")); mainLayout->addWidget(dontAskAgainCheckBox); buttonBox = new QDialogButtonBox(this); saveButton = buttonBox->addButton(QDialogButtonBox::Yes); saveButton->setObjectName(QStringLiteral("saveButton")); KGuiItem::assign(saveButton, KStandardGuiItem::saveAs()); saveButton->setDefault(true); openDefaultButton = new QPushButton; openDefaultButton->setObjectName(QStringLiteral("openDefaultButton")); buttonBox->addButton(openDefaultButton, QDialogButtonBox::ActionRole); openWithButton = new QPushButton; openWithButton->setObjectName(QStringLiteral("openWithButton")); buttonBox->addButton(openWithButton, QDialogButtonBox::ActionRole); QPushButton *cancelButton = buttonBox->addButton(QDialogButtonBox::Cancel); cancelButton->setObjectName(QStringLiteral("cancelButton")); connect(saveButton, &QPushButton::clicked, this, &BrowserOpenOrSaveQuestionPrivate::slotYesClicked); connect(openDefaultButton, &QPushButton::clicked, this, &BrowserOpenOrSaveQuestionPrivate::slotOpenDefaultClicked); connect(openWithButton, &QPushButton::clicked, this, &BrowserOpenOrSaveQuestionPrivate::slotOpenWithClicked); connect(buttonBox, &QDialogButtonBox::rejected, this, &BrowserOpenOrSaveQuestionPrivate::reject); mainLayout->addWidget(buttonBox); } bool autoEmbedMimeType(int flags); int executeDialog(const QString &dontShowAgainName) { KConfigGroup cg(dontAskConfig, "Notification Messages"); // group name comes from KMessageBox const QString dontAsk = cg.readEntry(dontShowAgainName, QString()).toLower(); if (dontAsk == QLatin1String("yes") || dontAsk == QLatin1String("true")) { return Save; } else if (dontAsk == QLatin1String("no") || dontAsk == QLatin1String("false")) { return OpenDefault; } const int result = exec(); if (dontAskAgainCheckBox->isChecked()) { cg.writeEntry(dontShowAgainName, result == BrowserOpenOrSaveQuestion::Save); cg.sync(); } return result; } void showService(KService::Ptr selectedService) { KGuiItem openItem(i18nc("@label:button", "&Open with %1", selectedService->name()), selectedService->icon()); KGuiItem::assign(openWithButton, openItem); } QUrl url; QString mimeType; QMimeType mime; KService::Ptr selectedService; KSqueezedTextLabel *questionLabel; BrowserOpenOrSaveQuestion::Features features; QLabel *fileNameLabel; QDialogButtonBox *buttonBox; QPushButton *saveButton; QPushButton *openDefaultButton; QPushButton *openWithButton; private: QCheckBox *dontAskAgainCheckBox; KSharedConfig::Ptr dontAskConfig; public Q_SLOTS: void reject() override { selectedService = nullptr; QDialog::reject(); } void slotYesClicked() { selectedService = nullptr; done(Save); } void slotOpenDefaultClicked() { done(OpenDefault); } void slotOpenWithClicked() { if (!openWithButton->menu()) { selectedService = nullptr; done(OpenWith); } } void slotAppSelected(QAction *action) { selectedService = action->data().value(); //showService(selectedService); done(OpenDefault); } }; BrowserOpenOrSaveQuestion::BrowserOpenOrSaveQuestion(QWidget *parent, const QUrl &url, const QString &mimeType) : d(new BrowserOpenOrSaveQuestionPrivate(parent, url, mimeType)) { } BrowserOpenOrSaveQuestion::~BrowserOpenOrSaveQuestion() { delete d; } static QAction *createAppAction(const KService::Ptr &service, QObject *parent) { QString actionName(service->name().replace(QLatin1Char('&'), QLatin1String("&&"))); actionName = i18nc("@action:inmenu", "Open &with %1", actionName); QAction *act = new QAction(parent); act->setIcon(QIcon::fromTheme(service->icon())); act->setText(actionName); act->setData(QVariant::fromValue(service)); return act; } BrowserOpenOrSaveQuestion::Result BrowserOpenOrSaveQuestion::askOpenOrSave() { d->questionLabel->setText(i18nc("@info", "Open '%1'?", d->url.toDisplayString(QUrl::PreferLocalFile))); d->questionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); d->openWithButton->hide(); KGuiItem openWithDialogItem(i18nc("@label:button", "&Open with..."), QStringLiteral("document-open")); // I thought about using KFileItemActions, but we don't want a submenu, nor the slots.... // and we want no menu at all if there's only one offer. // TODO: we probably need a setTraderConstraint(), to exclude the current application? const KService::List apps = KFileItemActions::associatedApplications(QStringList() << d->mimeType, QString() /* TODO trader constraint */); if (apps.isEmpty()) { KGuiItem::assign(d->openDefaultButton, openWithDialogItem); } else { KService::Ptr offer = apps.first(); KGuiItem openItem(i18nc("@label:button", "&Open with %1", offer->name()), offer->icon()); KGuiItem::assign(d->openDefaultButton, openItem); if (d->features & ServiceSelection) { // OpenDefault shall use this service d->selectedService = apps.first(); d->openWithButton->show(); QMenu *menu = new QMenu(d); if (apps.count() > 1) { // Provide an additional button with a menu of associated apps KGuiItem openWithItem(i18nc("@label:button", "&Open with"), QStringLiteral("document-open")); KGuiItem::assign(d->openWithButton, openWithItem); d->openWithButton->setMenu(menu); QObject::connect(menu, &QMenu::triggered, d, &BrowserOpenOrSaveQuestionPrivate::slotAppSelected); for (const auto &app : apps) { QAction *act = createAppAction(app, d); menu->addAction(act); } QAction *openWithDialogAction = new QAction(d); openWithDialogAction->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); openWithDialogAction->setText(openWithDialogItem.text()); menu->addAction(openWithDialogAction); } else { // Only one associated app, already offered by the other menu -> add "Open With..." button KGuiItem::assign(d->openWithButton, openWithDialogItem); } } else { // qDebug() << "Not using new feature ServiceSelection; port the caller to BrowserOpenOrSaveQuestion::setFeature(ServiceSelection)"; } } // KEEP IN SYNC with kdebase/runtime/keditfiletype/filetypedetails.cpp!!! const QString dontAskAgain = QLatin1String("askSave") + d->mimeType; const int choice = d->executeDialog(dontAskAgain); return choice == BrowserOpenOrSaveQuestionPrivate::Save ? Save : (choice == BrowserOpenOrSaveQuestionPrivate::Cancel ? Cancel : Open); } KService::Ptr BrowserOpenOrSaveQuestion::selectedService() const { return d->selectedService; } bool BrowserOpenOrSaveQuestionPrivate::autoEmbedMimeType(int flags) { // SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC // NOTE: Keep this function in sync with // kdebase/runtime/keditfiletype/filetypedetails.cpp // FileTypeDetails::updateAskSave() // Don't ask for: // - html (even new tabs would ask, due to about:blank!) // - dirs obviously (though not common over HTTP :), // - images (reasoning: no need to save, most of the time, because fast to see) // e.g. postscript is different, because takes longer to read, so // it's more likely that the user might want to save it. // - multipart/* ("server push", see kmultipart) // KEEP IN SYNC!!! if (flags != static_cast(BrowserOpenOrSaveQuestion::AttachmentDisposition) && mime.isValid() && ( mime.inherits(QStringLiteral("text/html")) || mime.inherits(QStringLiteral("application/xml")) || mime.inherits(QStringLiteral("inode/directory")) || mimeType.startsWith(QLatin1String("image")) || mime.inherits(QStringLiteral("multipart/x-mixed-replace")) || mime.inherits(QStringLiteral("multipart/replace")))) { return true; } return false; } BrowserOpenOrSaveQuestion::Result BrowserOpenOrSaveQuestion::askEmbedOrSave(int flags) { if (d->autoEmbedMimeType(flags)) { return Embed; } // don't use KStandardGuiItem::open() here which has trailing ellipsis! KGuiItem::assign(d->openDefaultButton, KGuiItem(i18nc("@label:button", "&Open"), QStringLiteral("document-open"))); d->openWithButton->hide(); d->questionLabel->setText(i18nc("@info", "Open '%1'?", d->url.toDisplayString(QUrl::PreferLocalFile))); d->questionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); const QString dontAskAgain = QLatin1String("askEmbedOrSave") + d->mimeType; // KEEP IN SYNC!!! // SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC const int choice = d->executeDialog(dontAskAgain); return choice == BrowserOpenOrSaveQuestionPrivate::Save ? Save : (choice == BrowserOpenOrSaveQuestionPrivate::Cancel ? Cancel : Embed); } void BrowserOpenOrSaveQuestion::setFeatures(Features features) { d->features = features; } void BrowserOpenOrSaveQuestion::setSuggestedFileName(const QString &suggestedFileName) { if (suggestedFileName.isEmpty()) { return; } d->fileNameLabel->setText(i18nc("@label File name", "Name: %1", suggestedFileName)); d->fileNameLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); d->fileNameLabel->setWhatsThis(i18nc("@info:whatsthis", "This is the file name suggested by the server")); d->fileNameLabel->show(); } #include "browseropenorsavequestion.moc" diff --git a/src/browserrun.cpp b/src/browserrun.cpp index ca023ad..ec6afc2 100644 --- a/src/browserrun.cpp +++ b/src/browserrun.cpp @@ -1,588 +1,589 @@ /* This file is part of the KDE project * * Copyright (C) 2002 David Faure * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License version 2, as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "browserrun.h" #include "browseropenorsavequestion.h" +#include "kparts_logging.h" + #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include #include #include #if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 71) using namespace KParts; class Q_DECL_HIDDEN BrowserRun::BrowserRunPrivate { public: bool m_bHideErrorDialog; bool m_bRemoveReferrer; bool m_bTrustedSource; KParts::OpenUrlArguments m_args; KParts::BrowserArguments m_browserArgs; KParts::ReadOnlyPart *m_part; // QGuardedPtr? QPointer m_window; QString m_mimeType; QString m_contentDisposition; }; BrowserRun::BrowserRun(const QUrl &url, const KParts::OpenUrlArguments &args, const KParts::BrowserArguments &browserArgs, KParts::ReadOnlyPart *part, QWidget *window, bool removeReferrer, bool trustedSource, bool hideErrorDialog) : KRun(url, window, false /* no GUI */), d(new BrowserRunPrivate) { d->m_bHideErrorDialog = hideErrorDialog; d->m_bRemoveReferrer = removeReferrer; d->m_bTrustedSource = trustedSource; d->m_args = args; d->m_browserArgs = browserArgs; d->m_part = part; d->m_window = window; } BrowserRun::~BrowserRun() { delete d; } KParts::ReadOnlyPart *BrowserRun::part() const { return d->m_part; } QUrl BrowserRun::url() const { return KRun::url(); } void BrowserRun::init() { if (d->m_bHideErrorDialog) { // ### KRun doesn't call a virtual method when it finds out that the URL // is either malformed, or points to a non-existing local file... // So we need to reimplement some of the checks, to handle d->m_bHideErrorDialog if (!KRun::url().isValid()) { redirectToError(KIO::ERR_MALFORMED_URL, KRun::url().toString()); return; } if (isLocalFile()) { const QString localPath = KRun::url().toLocalFile(); if (!QFile::exists(localPath)) { // qDebug() << localPath << "doesn't exist."; redirectToError(KIO::ERR_DOES_NOT_EXIST, localPath); return; } } } KRun::init(); } void BrowserRun::scanFile() { const QUrl url = KRun::url(); // qDebug() << url; // Let's check for well-known extensions // Not when there is a query in the URL, in any case. // Optimization for http/https, findByURL doesn't trust extensions over http. QString protocol = url.scheme(); if (!KProtocolInfo::proxiedBy(protocol).isEmpty()) { QString dummy; protocol = KProtocolManager::slaveProtocol(url, dummy); } if (!url.hasQuery() && !protocol.startsWith(QLatin1String("http")) && (!url.path().endsWith(QLatin1Char('/')) || KProtocolManager::supportsListing(url))) { QMimeDatabase db; QMimeType mime = db.mimeTypeForUrl(url); if (!mime.isDefault() || isLocalFile()) { // qDebug() << "MIME TYPE is" << mime.name(); mimeTypeDetermined(mime.name()); return; } } QMap &metaData = d->m_args.metaData(); if (d->m_part) { const QString proto = d->m_part->url().scheme(); if (proto == QLatin1String("https") || proto == QLatin1String("webdavs")) { metaData.insert(QStringLiteral("main_frame_request"), QStringLiteral("TRUE")); metaData.insert(QStringLiteral("ssl_was_in_use"), QStringLiteral("TRUE")); // metaData.insert(QStringLiteral("ssl_activate_warnings"), QStringLiteral("TRUE")); } else if (proto == QLatin1String("http") || proto == QLatin1String("webdav")) { // metaData.insert(QStringLiteral("ssl_activate_warnings"), QStringLiteral("TRUE")); metaData.insert(QStringLiteral("ssl_was_in_use"), QStringLiteral("FALSE")); } // Set the PropagateHttpHeader meta-data if it has not already been set... if (!metaData.contains(QStringLiteral("PropagateHttpHeader"))) { metaData.insert(QStringLiteral("PropagateHttpHeader"), QStringLiteral("TRUE")); } } KIO::TransferJob *job; if (d->m_browserArgs.doPost() && url.scheme().startsWith(QLatin1String("http"))) { job = KIO::http_post(url, d->m_browserArgs.postData, KIO::HideProgressInfo); job->addMetaData(QStringLiteral("content-type"), d->m_browserArgs.contentType()); } else { job = KIO::get(url, d->m_args.reload() ? KIO::Reload : KIO::NoReload, KIO::HideProgressInfo); } if (d->m_bRemoveReferrer) { metaData.remove(QStringLiteral("referrer")); } job->addMetaData(metaData); KJobWidgets::setWindow(job, d->m_window); connect(job, &KIO::TransferJob::result, this, &BrowserRun::slotBrowserScanFinished); connect(job, static_cast(&KIO::TransferJob::mimetype), this, &BrowserRun::slotBrowserMimetype); setJob(job); } void BrowserRun::slotBrowserScanFinished(KJob *job) { // qDebug() << job->error(); if (job->error() == KIO::ERR_IS_DIRECTORY) { // It is in fact a directory. This happens when HTTP redirects to FTP. // Due to the "protocol doesn't support listing" code in BrowserRun, we // assumed it was a file. // qDebug() << "It is in fact a directory!"; // Update our URL in case of a redirection KRun::setUrl(static_cast(job)->url()); setJob(nullptr); mimeTypeDetermined(QStringLiteral("inode/directory")); } else { KRun::slotScanFinished(job); } } static QMimeType fixupMimeType(const QString &mimeType, const QString &fileName) { QMimeDatabase db; QMimeType mime = db.mimeTypeForName(mimeType); if ((!mime.isValid() || mime.isDefault()) && !fileName.isEmpty()) { mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchExtension); } return mime; } void BrowserRun::slotBrowserMimetype(KIO::Job *_job, const QString &type) { Q_ASSERT(_job == KRun::job()); Q_UNUSED(_job) KIO::TransferJob *job = static_cast(KRun::job()); // Update our URL in case of a redirection //qDebug() << "old URL=" << KRun::url(); //qDebug() << "new URL=" << job->url(); setUrl(job->url()); if (job->isErrorPage()) { d->m_mimeType = type; handleError(job); setJob(nullptr); } else { // qDebug() << "found" << type << "for" << KRun::url(); // Suggested filename given by the server (e.g. HTTP content-disposition) // When set, we should really be saving instead of embedding const QString suggestedFileName = job->queryMetaData(QStringLiteral("content-disposition-filename")); setSuggestedFileName(suggestedFileName); // store it (in KRun) //qDebug() << "suggestedFileName=" << suggestedFileName; d->m_contentDisposition = job->queryMetaData(QStringLiteral("content-disposition-type")); const QString modificationTime = job->queryMetaData(QStringLiteral("content-disposition-modification-date")); if (!modificationTime.isEmpty()) { d->m_args.metaData().insert(QStringLiteral("content-disposition-modification-date"), modificationTime); } QMapIterator it(job->metaData()); while (it.hasNext()) { it.next(); if (it.key().startsWith(QLatin1String("ssl_"), Qt::CaseInsensitive)) { d->m_args.metaData().insert(it.key(), it.value()); } } // Make a copy to avoid a dead reference QString _type = type; job->putOnHold(); setJob(nullptr); // If the current mime-type is the default mime-type, then attempt to // determine the "real" mimetype from the file name. QMimeType mime = fixupMimeType(_type, suggestedFileName.isEmpty() ? url().fileName() : suggestedFileName); if (mime.isValid() && mime.name() != _type) { _type = mime.name(); } mimeTypeDetermined(_type); } } BrowserRun::NonEmbeddableResult BrowserRun::handleNonEmbeddable(const QString &mimeType) { return handleNonEmbeddable(mimeType, nullptr); } BrowserRun::NonEmbeddableResult BrowserRun::handleNonEmbeddable(const QString &_mimeType, KService::Ptr *selectedService) { QString mimeType(_mimeType); Q_ASSERT(!hasFinished()); // only come here if the mimetype couldn't be embedded // Support for saving remote files. if (mimeType != QLatin1String("inode/directory") && // dirs can't be saved !KRun::url().isLocalFile()) { if (isTextExecutable(mimeType)) { mimeType = QStringLiteral("text/plain"); // view, don't execute } // ... -> ask whether to save BrowserOpenOrSaveQuestion question(d->m_window, KRun::url(), mimeType); question.setSuggestedFileName(suggestedFileName()); if (selectedService) { question.setFeatures(BrowserOpenOrSaveQuestion::ServiceSelection); } BrowserOpenOrSaveQuestion::Result res = question.askOpenOrSave(); if (res == BrowserOpenOrSaveQuestion::Save) { save(KRun::url(), suggestedFileName()); // qDebug() << "Save: returning Handled"; setFinished(true); return Handled; } else if (res == BrowserOpenOrSaveQuestion::Cancel) { // saving done or canceled // qDebug() << "Cancel: returning Handled"; setFinished(true); return Handled; } else { // "Open" chosen (done by KRun::foundMimeType, called when returning NotHandled) // If we were in a POST, we can't just pass a URL to an external application. // We must save the data to a tempfile first. if (d->m_browserArgs.doPost()) { // qDebug() << "request comes from a POST, can't pass a URL to another app, need to save"; d->m_mimeType = mimeType; QString extension; QString fileName = suggestedFileName().isEmpty() ? KRun::url().fileName() : suggestedFileName(); int extensionPos = fileName.lastIndexOf(QLatin1Char('.')); if (extensionPos != -1) { extension = fileName.mid(extensionPos); // keep the '.' } QTemporaryFile tempFile(QDir::tempPath() + QLatin1Char('/') + QCoreApplication::applicationName() + QLatin1String("XXXXXX") + extension); tempFile.setAutoRemove(false); tempFile.open(); QUrl destURL = QUrl::fromLocalFile(tempFile.fileName()); KIO::Job *job = KIO::file_copy(KRun::url(), destURL, 0600, KIO::Overwrite); KJobWidgets::setWindow(job, d->m_window); connect(job, &KIO::Job::result, this, &BrowserRun::slotCopyToTempFileResult); return Delayed; // We'll continue after the job has finished } if (selectedService && question.selectedService()) { *selectedService = question.selectedService(); // KRun will use this when starting an app KRun::setPreferredService(question.selectedService()->desktopEntryName()); } } } // Check if running is allowed if (!d->m_bTrustedSource && // ... and untrusted source... !allowExecution(mimeType, KRun::url())) { // ...and the user said no (for executables etc.) setFinished(true); return Handled; } KIO::Scheduler::publishSlaveOnHold(); // publish any slave on hold so it can be reused. return NotHandled; } //static bool BrowserRun::allowExecution(const QString &mimeType, const QUrl &url) { if (!KRun::isExecutable(mimeType)) { return true; } if (!url.isLocalFile()) { // Don't permit to execute remote files return false; } return (KMessageBox::warningContinueCancel(nullptr, i18n("Do you really want to execute '%1'?", url.toDisplayString()), i18n("Execute File?"), KGuiItem(i18n("Execute"))) == KMessageBox::Continue); } //static, deprecated #if KPARTS_BUILD_DEPRECATED_SINCE(5, 0) BrowserRun::AskSaveResult BrowserRun::askSave(const QUrl &url, KService::Ptr offer, const QString &mimeType, const QString &suggestedFileName) { Q_UNUSED(offer); BrowserOpenOrSaveQuestion question(nullptr, url, mimeType); question.setSuggestedFileName(suggestedFileName); const BrowserOpenOrSaveQuestion::Result result = question.askOpenOrSave(); return result == BrowserOpenOrSaveQuestion::Save ? Save : BrowserOpenOrSaveQuestion::Open ? Open : Cancel; } #endif //static, deprecated #if KPARTS_BUILD_DEPRECATED_SINCE(5, 0) BrowserRun::AskSaveResult BrowserRun::askEmbedOrSave(const QUrl &url, const QString &mimeType, const QString &suggestedFileName, int flags) { BrowserOpenOrSaveQuestion question(nullptr, url, mimeType); question.setSuggestedFileName(suggestedFileName); const BrowserOpenOrSaveQuestion::Result result = question.askEmbedOrSave(flags); return result == BrowserOpenOrSaveQuestion::Save ? Save : result == BrowserOpenOrSaveQuestion::Embed ? Open : Cancel; } #endif // Default implementation, overridden in KHTMLRun void BrowserRun::save(const QUrl &url, const QString &suggestedFileName) { saveUrl(url, suggestedFileName, d->m_window, d->m_args); } #if KPARTS_BUILD_DEPRECATED_SINCE(4, 4) // static void BrowserRun::simpleSave(const QUrl &url, const QString &suggestedFileName, QWidget *window) { saveUrl(url, suggestedFileName, window, KParts::OpenUrlArguments()); } #endif void KParts::BrowserRun::saveUrl(const QUrl &url, const QString &suggestedFileName, QWidget *window, const KParts::OpenUrlArguments &args) { // DownloadManager <-> konqueror integration // find if the integration is enabled // the empty key means no integration // only use the downloadmanager for non-local urls if (!url.isLocalFile()) { KConfigGroup cfg = KSharedConfig::openConfig(QStringLiteral("konquerorrc"), KConfig::NoGlobals)->group("HTML Settings"); QString downloadManager = cfg.readPathEntry("DownloadManager", QString()); if (!downloadManager.isEmpty()) { // then find the download manager location // qDebug() << "Using: "<setExecutable(downloadManager); job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, window)); job->start(); return; } } } // no download manager available, let's do it ourself QFileDialog *dlg = new QFileDialog(window); dlg->setAcceptMode(QFileDialog::AcceptSave); dlg->setWindowTitle(i18n("Save As")); dlg->setOption(QFileDialog::DontConfirmOverwrite, false); QString name; if (!suggestedFileName.isEmpty()) { name = suggestedFileName; } else { name = url.fileName(); // can be empty, e.g. in case http://www.kde.org/ } dlg->selectFile(name); if (dlg->exec()) { QUrl destURL(dlg->selectedUrls().at(0)); if (destURL.isValid()) { saveUrlUsingKIO(url, destURL, window, args.metaData()); } } delete dlg; } void BrowserRun::saveUrlUsingKIO(const QUrl &srcUrl, const QUrl &destUrl, QWidget *window, const QMap &metaData) { KIO::FileCopyJob *job = KIO::file_copy(srcUrl, destUrl, -1, KIO::Overwrite); const QString modificationTime = metaData[QStringLiteral("content-disposition-modification-date")]; if (!modificationTime.isEmpty()) { job->setModificationTime(QDateTime::fromString(modificationTime, Qt::RFC2822Date)); } job->setMetaData(metaData); job->addMetaData(QStringLiteral("MaxCacheSize"), QStringLiteral("0")); // Don't store in http cache. job->addMetaData(QStringLiteral("cache"), QStringLiteral("cache")); // Use entry from cache if available. KJobWidgets::setWindow(job, window); job->uiDelegate()->setAutoErrorHandlingEnabled(true); } void BrowserRun::handleError(KJob *job) { if (!job) { // Shouldn't happen - qWarning() << "handleError called with job=0! hideErrorDialog=" << d->m_bHideErrorDialog; + qCWarning(KPARTSLOG) << "handleError called with job=0! hideErrorDialog=" << d->m_bHideErrorDialog; return; } KIO::TransferJob *tjob = qobject_cast(job); if (tjob && tjob->isErrorPage() && !job->error()) { // The default handling of error pages is to show them like normal pages // But this is done here in handleError so that KHTMLRun can reimplement it tjob->putOnHold(); setJob(nullptr); if (!d->m_mimeType.isEmpty()) { mimeTypeDetermined(d->m_mimeType); } return; } if (d->m_bHideErrorDialog && job->error() != KIO::ERR_NO_CONTENT) { redirectToError(job->error(), job->errorText()); return; } // Reuse code in KRun, to benefit from d->m_showingError etc. KRun::handleError(job); } // static QUrl BrowserRun::makeErrorUrl(int error, const QString &errorText, const QUrl &initialUrl) { /* * The format of the error:/ URL is error:/?query#url, * where two variables are passed in the query: * error = int kio error code, errText = QString error text from kio * The sub-url is the URL that we were trying to open. */ QUrl newURL(QStringLiteral("error:/?error=%1&errText=%2") .arg(error) .arg(QString::fromUtf8(QUrl::toPercentEncoding(errorText)))); QString cleanedOrigUrl = initialUrl.toString(); QUrl runURL(cleanedOrigUrl); if (runURL.isValid()) { runURL.setPassword(QString()); // don't put the password in the error URL cleanedOrigUrl = runURL.toString(); } newURL.setFragment(cleanedOrigUrl); return newURL; } void BrowserRun::redirectToError(int error, const QString &errorText) { /** * To display this error in KHTMLPart instead of inside a dialog box, * we tell konq that the mimetype is text/html, and we redirect to * an error:/ URL that sends the info to khtml. */ KRun::setUrl(makeErrorUrl(error, errorText, url())); setJob(nullptr); mimeTypeDetermined(QStringLiteral("text/html")); } void BrowserRun::slotCopyToTempFileResult(KJob *job) { if (job->error()) { job->uiDelegate()->showErrorMessage(); } else { // Same as KRun::foundMimeType but with a different URL const QUrl destUrl = static_cast(job)->destUrl(); KIO::OpenUrlJob *job = new KIO::OpenUrlJob(destUrl, d->m_mimeType); job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, d->m_window)); job->setRunExecutables(true); job->start(); } setError(true); // see above setFinished(true); } bool BrowserRun::isTextExecutable(const QString &mimeType) { return (mimeType == QLatin1String("application/x-desktop") || mimeType == QLatin1String("application/x-shellscript")); } bool BrowserRun::hideErrorDialog() const { return d->m_bHideErrorDialog; } QString BrowserRun::contentDisposition() const { return d->m_contentDisposition; } bool BrowserRun::serverSuggestsSave() const { // RfC 2183, section 2.8: // Unrecognized disposition types should be treated as `attachment'. return !contentDisposition().isEmpty() && (contentDisposition() != QLatin1String("inline")); } KParts::OpenUrlArguments &KParts::BrowserRun::arguments() { return d->m_args; } KParts::BrowserArguments &KParts::BrowserRun::browserArguments() { return d->m_browserArgs; } #include "moc_browserrun.cpp" #endif diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6e01f46..067cc2a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,216 +1,214 @@ /* This file is part of the KDE project Copyright (C) 1999 Simon Hausmann (C) 1999 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mainwindow.h" #include "guiactivateevent.h" #include "part.h" #include "plugin.h" #include #include #include #include #include #include #include #include #include #include #include -#include - using namespace KParts; namespace KParts { class MainWindowPrivate { public: MainWindowPrivate() : m_activePart(nullptr), m_bShellGUIActivated(false), m_helpMenu(nullptr), m_manageWindowTitle(true) { } ~MainWindowPrivate() { } QPointer m_activePart; bool m_bShellGUIActivated; KHelpMenu *m_helpMenu; bool m_manageWindowTitle; }; } MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags f) : KXmlGuiWindow(parent, f), d(new MainWindowPrivate()) { PartBase::setPartObject(this); } MainWindow::~MainWindow() { delete d; } void MainWindow::createGUI(Part *part) { #if 0 // qDebug() << "part=" << part << (part ? part->metaObject()->className() : "") << (part ? part->objectName() : ""); #endif KXMLGUIFactory *factory = guiFactory(); Q_ASSERT(factory); if (d->m_activePart) { #if 0 // qDebug() << "deactivating GUI for" << d->m_activePart << d->m_activePart->metaObject()->className() << d->m_activePart->objectName(); #endif GUIActivateEvent ev(false); QApplication::sendEvent(d->m_activePart, &ev); factory->removeClient(d->m_activePart); disconnect(d->m_activePart.data(), &Part::setWindowCaption, this, static_cast(&MainWindow::setCaption)); disconnect(d->m_activePart.data(), &Part::setStatusBarText, this, &MainWindow::slotSetStatusBarText); } if (!d->m_bShellGUIActivated) { loadPlugins(this, this, KAboutData::applicationData()); createShellGUI(); d->m_bShellGUIActivated = true; } if (part) { // do this before sending the activate event if (d->m_manageWindowTitle) { connect(part, &Part::setWindowCaption, this, static_cast(&MainWindow::setCaption)); } connect(part, &Part::setStatusBarText, this, &MainWindow::slotSetStatusBarText); factory->addClient(part); GUIActivateEvent ev(true); QApplication::sendEvent(part, &ev); } d->m_activePart = part; } void MainWindow::slotSetStatusBarText(const QString &text) { statusBar()->showMessage(text); } void MainWindow::createShellGUI(bool create) { Q_ASSERT(d->m_bShellGUIActivated != create); d->m_bShellGUIActivated = create; if (create) { if (isHelpMenuEnabled() && !d->m_helpMenu) { d->m_helpMenu = new KHelpMenu(this, KAboutData::applicationData(), true); KActionCollection *actions = actionCollection(); QAction *helpContentsAction = d->m_helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->m_helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->m_helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->m_helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->m_helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->m_helpMenu->action(KHelpMenu::menuAboutKDE); QAction *donateAction = d->m_helpMenu->action(KHelpMenu::menuDonate); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } if (donateAction) { actions->addAction(donateAction->objectName(), donateAction); } } QString f = xmlFile(); setXMLFile(KXMLGUIClient::standardsXmlFileLocation()); if (!f.isEmpty()) { setXMLFile(f, true); } else { QString auto_file(componentName() + QLatin1String("ui.rc")); setXMLFile(auto_file, true); } GUIActivateEvent ev(true); QApplication::sendEvent(this, &ev); guiFactory()->addClient(this); checkAmbiguousShortcuts(); } else { GUIActivateEvent ev(false); QApplication::sendEvent(this, &ev); guiFactory()->removeClient(this); } } void KParts::MainWindow::setWindowTitleHandling(bool enabled) { d->m_manageWindowTitle = enabled; } void KParts::MainWindow::saveNewToolbarConfig() { createGUI(d->m_activePart); KConfigGroup cg(KSharedConfig::openConfig(), QString()); applyMainWindowSettings(cg); } void KParts::MainWindow::configureToolbars() { // No difference with base class anymore. KXmlGuiWindow::configureToolbars(); } diff --git a/src/partloader.cpp b/src/partloader.cpp index 863f5d0..1a69379 100644 --- a/src/partloader.cpp +++ b/src/partloader.cpp @@ -1,199 +1,200 @@ /* This file is part of the KDE project Copyright (C) 2020 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "partloader.h" +#include "kparts_logging.h" + #include #include #include #include #include #include -#include #include #include // TODO KF6 REMOVE #if KPARTS_VERSION <= QT_VERSION_CHECK(5, 900, 0) #include #include #include #include #endif // We still use desktop files for translated descriptions in keditfiletype, // and desktop file names then end up in mimeapps.list. // Alternatively, that KCM could be ported to read the descriptions from the JSON metadata? // KF6 TODO: at least make the KCM write out library names (into a different config file) // so we don't need to do the lookup here every time. static QString pluginForDesktopFile(const QString &desktopFile) { KService::Ptr service = KService::serviceByStorageId(desktopFile); if (!service) { - qDebug() << "mimeapps.list specifies unknown service" << desktopFile; + qCDebug(KPARTSLOG) << "mimeapps.list specifies unknown service" << desktopFile; return {}; } return service->library(); } static QStringList partsFromUserPreference(const QString &mimeType) { auto config = KSharedConfig::openConfig(QStringLiteral("mimeapps.list")); const QStringList desktopFiles = config->group(QStringLiteral("Added KDE Service Associations")).readXdgListEntry(mimeType); QStringList parts; parts.reserve(desktopFiles.count()); for (const QString &desktopFile : desktopFiles) { const QString part = pluginForDesktopFile(desktopFile); if (!part.isEmpty()) { parts.append(part); } } return parts; } // A plugin can support N mimetypes. Pick the one that is closest to @parent in the inheritance tree // and return how far it is from that parent (0 = same mimetype, 1 = direct child, etc.) static int pluginDistanceToMimeType(const KPluginMetaData &md, const QString &parent) { QMimeDatabase db; auto distanceToMimeType = [&](const QString &mime) { if (mime == parent) return 0; const QStringList ancestors = db.mimeTypeForName(mime).allAncestors(); const int dist = ancestors.indexOf(parent); return dist == -1 ? 50 : dist + 1; }; const QStringList mimes = md.mimeTypes(); int minDistance = 50; for (const QString &mime : mimes) { minDistance = std::min(minDistance, distanceToMimeType(mime)); } return minDistance; } QVector KParts::PartLoader::partsForMimeType(const QString &mimeType) { auto supportsMime = [&](const KPluginMetaData &md) { return md.supportsMimeType(mimeType); }; QVector plugins = KPluginLoader::findPlugins(QStringLiteral("kf5/parts"), supportsMime); #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 0) // KF5 compat code // I would compare library filenames, but KPluginMetaData::fileName looks like kf5/kparts/okteta and KService::library() is a full path // The user actually sees translated names, let's ensure those don't look duplicated in the list. auto isPluginForName = [](const QString &name) { return [name](const KPluginMetaData &plugin) { return plugin.name() == name; }; }; const KService::List offers = KMimeTypeTrader::self()->query(mimeType, QStringLiteral("KParts/ReadOnlyPart")); for (const KService::Ptr &service : offers) { QT_WARNING_PUSH QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations") QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations") KPluginInfo info(service); QT_WARNING_POP if (info.isValid()) { if (std::find_if(plugins.cbegin(), plugins.cend(), isPluginForName(info.name())) == plugins.cend()) { plugins.append(info.toMetaData()); } } } #endif auto orderPredicate = [&](const KPluginMetaData &left, const KPluginMetaData &right) { // We filtered based on "supports mimetype", but this didn't order from most-specific to least-specific. const int leftDistance = pluginDistanceToMimeType(left, mimeType); const int rightDistance = pluginDistanceToMimeType(right, mimeType); if (leftDistance < rightDistance) return true; if (leftDistance > rightDistance) return false; // Plugins who support the same mimetype are then sorted by initial preference return left.initialPreference() > right.initialPreference(); }; std::sort(plugins.begin(), plugins.end(), orderPredicate); const QStringList userParts = partsFromUserPreference(mimeType); if (!userParts.isEmpty()) { //for (const KPluginMetaData &plugin : plugins) { // qDebug() << "unsorted:" << plugin.fileName() << plugin.initialPreference(); //} const auto defaultPlugins = plugins; plugins.clear(); for (const QString &userPart : userParts) { // e.g. kf5/parts/gvpart auto matchesLibrary = [&](const KPluginMetaData &plugin) { return plugin.fileName().contains(userPart); }; auto it = std::find_if(defaultPlugins.begin(), defaultPlugins.end(), matchesLibrary); if (it != defaultPlugins.end()) { plugins.push_back(*it); } else { - qDebug() << "Part not found" << userPart; + qCDebug(KPARTSLOG) << "Part not found" << userPart; } } // In case mimeapps.list lists "nothing good", append the default set to the end as fallback plugins += defaultPlugins; } //for (const KPluginMetaData &plugin : plugins) { // qDebug() << plugin.fileName() << plugin.initialPreference(); //} return plugins; } // KF6 TODO: make create(const char* iface...) public in KPluginFactory, remove this hack class KPluginFactoryHack : public KPluginFactory { public: QObject *create(const char *iface, QWidget *parentWidget, QObject *parent, const QVariantList &args, const QString &keyword) override { return KPluginFactory::create(iface, parentWidget, parent, args, keyword); } }; QObject *KParts::PartLoader::Private::createPartInstanceForMimeTypeHelper(const char *iface, const QString &mimeType, QWidget *parentWidget, QObject *parent, QString *error) { const QVector plugins = KParts::PartLoader::partsForMimeType(mimeType); for (const KPluginMetaData &plugin : plugins) { KPluginLoader pluginLoader(plugin.fileName()); // ## How can the keyword feature work with JSON metadata? // ## Several desktop files could point to the same .so file, but a .so file only has one metadata builtin. // ## Unlikely to be a problem here (multiple KParts in the same .so file?), but to be solved for KCMs with an array of "KPlugin" in the JSON file... // ## It would then be used by KPluginLoader::findPlugins, and we'd use plugin.keyword() here. const QString pluginKeyword; KPluginFactory *factory = pluginLoader.factory(); if (factory) { QObject *obj = static_cast(factory)->create(iface, parentWidget, parent, QVariantList(), pluginKeyword); if (error) { if (!obj) { *error = i18n("The plugin '%1' does not provide an interface '%2' with keyword '%3'", plugin.fileName(), QString::fromLatin1(iface), pluginKeyword); } else { error->clear(); } } if (obj) { return obj; } } else if (error) { *error = pluginLoader.errorString(); } pluginLoader.unload(); } if (error) { *error = i18n("No part was found for mimeType %1", mimeType); } return nullptr; } diff --git a/src/partmanager.cpp b/src/partmanager.cpp index 5803440..c43d36b 100644 --- a/src/partmanager.cpp +++ b/src/partmanager.cpp @@ -1,585 +1,579 @@ /* This file is part of the KDE project Copyright (C) 1999 Simon Hausmann (C) 1999 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "partmanager.h" #include "kparts_logging.h" #include "partactivateevent.h" #include "partselectevent.h" #include "guiactivateevent.h" #include "part.h" #include #include #include -#include - -//#define DEBUG_PARTMANAGER - using namespace KParts; namespace KParts { class PartManagerPrivate { public: PartManagerPrivate() { m_activeWidget = nullptr; m_activePart = nullptr; m_selectedPart = nullptr; m_selectedWidget = nullptr; m_bAllowNestedParts = false; m_bIgnoreScrollBars = false; m_activationButtonMask = Qt::LeftButton | Qt::MidButton | Qt::RightButton; m_reason = PartManager::NoReason; m_bIgnoreExplicitFocusRequest = false; } ~PartManagerPrivate() { } void setReason(QEvent *ev) { switch (ev->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonDblClick: { QMouseEvent *mev = static_cast(ev); m_reason = mev->button() == Qt::LeftButton ? PartManager::ReasonLeftClick : (mev->button() == Qt::MidButton ? PartManager::ReasonMidClick : PartManager::ReasonRightClick); break; } case QEvent::FocusIn: m_reason = static_cast(ev)->reason(); break; default: - qWarning() << "PartManagerPrivate::setReason got unexpected ev type " << ev->type(); + qCWarning(KPARTSLOG) << "PartManagerPrivate::setReason got unexpected event type" << ev->type(); break; } } bool allowExplicitFocusEvent(QEvent *ev) const { if (ev->type() == QEvent::FocusIn) { QFocusEvent *fev = static_cast(ev); return (!m_bIgnoreExplicitFocusRequest || fev->reason() != Qt::OtherFocusReason); } return true; } Part *m_activePart; QWidget *m_activeWidget; QList m_parts; PartManager::SelectionPolicy m_policy; Part *m_selectedPart; QWidget *m_selectedWidget; QList m_managedTopLevelWidgets; short int m_activationButtonMask; bool m_bIgnoreScrollBars; bool m_bAllowNestedParts; int m_reason; bool m_bIgnoreExplicitFocusRequest; }; } PartManager::PartManager(QWidget *parent) : QObject(parent), d(new PartManagerPrivate) { qApp->installEventFilter(this); d->m_policy = Direct; addManagedTopLevelWidget(parent); } PartManager::PartManager(QWidget *topLevel, QObject *parent) : QObject(parent), d(new PartManagerPrivate) { qApp->installEventFilter(this); d->m_policy = Direct; addManagedTopLevelWidget(topLevel); } PartManager::~PartManager() { for (const QWidget *w : qAsConst(d->m_managedTopLevelWidgets)) { disconnect(w, &QWidget::destroyed, this, &PartManager::slotManagedTopLevelWidgetDestroyed); } for (Part *it : qAsConst(d->m_parts)) { it->setManager(nullptr); } // core dumps ... setActivePart( 0 ); qApp->removeEventFilter(this); delete d; } void PartManager::setSelectionPolicy(SelectionPolicy policy) { d->m_policy = policy; } PartManager::SelectionPolicy PartManager::selectionPolicy() const { return d->m_policy; } void PartManager::setAllowNestedParts(bool allow) { d->m_bAllowNestedParts = allow; } bool PartManager::allowNestedParts() const { return d->m_bAllowNestedParts; } void PartManager::setIgnoreScrollBars(bool ignore) { d->m_bIgnoreScrollBars = ignore; } bool PartManager::ignoreScrollBars() const { return d->m_bIgnoreScrollBars; } void PartManager::setActivationButtonMask(short int buttonMask) { d->m_activationButtonMask = buttonMask; } short int PartManager::activationButtonMask() const { return d->m_activationButtonMask; } bool PartManager::eventFilter(QObject *obj, QEvent *ev) { if (ev->type() != QEvent::MouseButtonPress && ev->type() != QEvent::MouseButtonDblClick && ev->type() != QEvent::FocusIn) { return false; } if (!obj->isWidgetType()) { return false; } QWidget *w = static_cast(obj); if (((w->windowFlags().testFlag(Qt::Dialog)) && w->isModal()) || (w->windowFlags().testFlag(Qt::Popup)) || (w->windowFlags().testFlag(Qt::Tool))) { return false; } QMouseEvent *mev = nullptr; if (ev->type() == QEvent::MouseButtonPress || ev->type() == QEvent::MouseButtonDblClick) { mev = static_cast(ev); qCDebug(KPARTSLOG) << "PartManager::eventFilter button:" << mev->button() << "d->m_activationButtonMask=" << d->m_activationButtonMask; if ((mev->button() & d->m_activationButtonMask) == 0) { return false; // ignore this button } } Part *part; while (w) { QPoint pos; if (!d->m_managedTopLevelWidgets.contains(w->topLevelWidget())) { return false; } if (d->m_bIgnoreScrollBars && ::qobject_cast(w)) { return false; } if (mev) { // mouse press or mouse double-click event pos = mev->globalPos(); part = findPartFromWidget(w, pos); } else { part = findPartFromWidget(w); } const char *evType = (ev->type() == QEvent::MouseButtonPress) ? "MouseButtonPress" : (ev->type() == QEvent::MouseButtonDblClick) ? "MouseButtonDblClick" : (ev->type() == QEvent::FocusIn) ? "FocusIn" : "OTHER! ERROR!"; if (part) { // We found a part whose widget is w if (d->m_policy == PartManager::TriState) { if (ev->type() == QEvent::MouseButtonDblClick) { if (part == d->m_activePart && w == d->m_activeWidget) { return false; } qCDebug(KPARTSLOG) << "PartManager::eventFilter dblclick -> setActivePart" << part; d->setReason(ev); setActivePart(part, w); d->m_reason = NoReason; return true; } if ((d->m_selectedWidget != w || d->m_selectedPart != part) && (d->m_activeWidget != w || d->m_activePart != part)) { if (part->isSelectable()) { setSelectedPart(part, w); } else { qCDebug(KPARTSLOG) << "Part" << part << "(non-selectable) made active because" << w->metaObject()->className() << "got event" << evType; d->setReason(ev); setActivePart(part, w); d->m_reason = NoReason; } return true; } else if (d->m_selectedWidget == w && d->m_selectedPart == part) { qCDebug(KPARTSLOG) << "Part" << part << "made active (from selected) because" << w->metaObject()->className() << "got event" << evType; d->setReason(ev); setActivePart(part, w); d->m_reason = NoReason; return true; } else if (d->m_activeWidget == w && d->m_activePart == part) { setSelectedPart(nullptr); return false; } return false; } else if (part != d->m_activePart && d->allowExplicitFocusEvent(ev)) { qCDebug(KPARTSLOG) << "Part" << part << "made active because" << w->metaObject()->className() << "got event" << evType; d->setReason(ev); setActivePart(part, w); d->m_reason = NoReason; } return false; } w = w->parentWidget(); if (w && (((w->windowFlags() & Qt::Dialog) && w->isModal()) || (w->windowFlags() & Qt::Popup) || (w->windowFlags() & Qt::Tool))) { qCDebug(KPARTSLOG) << "No part made active although" << obj->objectName() << "/" << obj->metaObject()->className() << "got event - loop aborted"; return false; } } qCDebug(KPARTSLOG) << "No part made active although" << obj->objectName() << "/" << obj->metaObject()->className() << "got event - loop aborted"; return false; } Part *PartManager::findPartFromWidget(QWidget *widget, const QPoint &pos) { for (auto *p : qAsConst(d->m_parts)) { Part *part = p->hitTest(widget, pos); if (part && d->m_parts.contains(part)) { return part; } } return nullptr; } Part *PartManager::findPartFromWidget(QWidget *widget) { for (auto *part : qAsConst(d->m_parts)) { if (widget == part->widget()) { return part; } } return nullptr; } void PartManager::addPart(Part *part, bool setActive) { Q_ASSERT(part); // don't add parts more than once :) if (d->m_parts.contains(part)) { -#ifdef DEBUG_PARTMANAGER - qWarning() << part << " already added"; -#endif + qCWarning(KPARTSLOG) << part << " already added"; return; } d->m_parts.append(part); part->setManager(this); if (setActive) { setActivePart(part); if (QWidget *w = part->widget()) { // Prevent focus problems if (w->focusPolicy() == Qt::NoFocus) { - qWarning() << "Part '" << part->objectName() << "' has a widget " - << w->objectName() << " with a focus policy of NoFocus. It should have at least a" - << "ClickFocus policy, for part activation to work well."; + qCWarning(KPARTSLOG) << "Part '" << part->objectName() << "' has a widget " << w->objectName() + << "with a focus policy of NoFocus. It should have at least a" + << "ClickFocus policy, for part activation to work well."; } if (part->widget() && part->widget()->focusPolicy() == Qt::TabFocus) { - qWarning() << "Part '" << part->objectName() << "' has a widget " - << w->objectName() << " with a focus policy of TabFocus. It should have at least a" - << "ClickFocus policy, for part activation to work well."; + qCWarning(KPARTSLOG) << "Part '" << part->objectName() << "' has a widget " << w->objectName() + << "with a focus policy of TabFocus. It should have at least a" + << "ClickFocus policy, for part activation to work well."; } w->setFocus(); w->show(); } } emit partAdded(part); } void PartManager::removePart(Part *part) { if (!d->m_parts.contains(part)) { return; } const int nb = d->m_parts.removeAll(part); Q_ASSERT(nb == 1); Q_UNUSED(nb); // no warning in release mode part->setManager(nullptr); emit partRemoved(part); if (part == d->m_activePart) { setActivePart(nullptr); } if (part == d->m_selectedPart) { setSelectedPart(nullptr); } } void PartManager::replacePart(Part *oldPart, Part *newPart, bool setActive) { //qCDebug(KPARTSLOG) << "replacePart" << oldPart->name() << "->" << newPart->name() << "setActive=" << setActive; // This methods does exactly removePart + addPart but without calling setActivePart(0) in between if (!d->m_parts.contains(oldPart)) { qFatal("Can't remove part %s, not in KPartManager's list.", oldPart->objectName().toLocal8Bit().constData()); return; } d->m_parts.removeAll(oldPart); oldPart->setManager(nullptr); emit partRemoved(oldPart); addPart(newPart, setActive); } void PartManager::setActivePart(Part *part, QWidget *widget) { if (part && !d->m_parts.contains(part)) { - qWarning() << "trying to activate a non-registered part!" << part->objectName(); + qCWarning(KPARTSLOG) << "trying to activate a non-registered part!" << part->objectName(); return; // don't allow someone call setActivePart with a part we don't know about } //check whether nested parts are disallowed and activate the top parent part then, by traversing the //tree recursively (Simon) if (part && !d->m_bAllowNestedParts) { QObject *parentPart = part->parent(); // ### this relies on people using KParts::Factory! KParts::Part *parPart = ::qobject_cast(parentPart); if (parPart) { setActivePart(parPart, parPart->widget()); return; } } qCDebug(KPARTSLOG) << "PartManager::setActivePart d->m_activePart=" << d->m_activePart << "<->part=" << part << "d->m_activeWidget=" << d->m_activeWidget << "<->widget=" << widget; // don't activate twice if (d->m_activePart && part && d->m_activePart == part && (!widget || d->m_activeWidget == widget)) { return; } KParts::Part *oldActivePart = d->m_activePart; QWidget *oldActiveWidget = d->m_activeWidget; setSelectedPart(nullptr); d->m_activePart = part; d->m_activeWidget = widget; if (oldActivePart) { KParts::Part *savedActivePart = part; QWidget *savedActiveWidget = widget; PartActivateEvent ev(false, oldActivePart, oldActiveWidget); QApplication::sendEvent(oldActivePart, &ev); if (oldActiveWidget) { disconnect(oldActiveWidget, &QWidget::destroyed, this, &PartManager::slotWidgetDestroyed); QApplication::sendEvent(oldActiveWidget, &ev); } d->m_activePart = savedActivePart; d->m_activeWidget = savedActiveWidget; } if (d->m_activePart) { if (!widget) { d->m_activeWidget = part->widget(); } PartActivateEvent ev(true, d->m_activePart, d->m_activeWidget); QApplication::sendEvent(d->m_activePart, &ev); if (d->m_activeWidget) { connect(d->m_activeWidget, &QWidget::destroyed, this, &PartManager::slotWidgetDestroyed); QApplication::sendEvent(d->m_activeWidget, &ev); } } // Set the new active instance //setActiveComponent(d->m_activePart ? d->m_activePart->componentData() : KComponentData::mainComponent()); qCDebug(KPARTSLOG) << this << "emitting activePartChanged" << d->m_activePart; emit activePartChanged(d->m_activePart); } Part *PartManager::activePart() const { return d->m_activePart; } QWidget *PartManager::activeWidget() const { return d->m_activeWidget; } void PartManager::setSelectedPart(Part *part, QWidget *widget) { if (part == d->m_selectedPart && widget == d->m_selectedWidget) { return; } Part *oldPart = d->m_selectedPart; QWidget *oldWidget = d->m_selectedWidget; d->m_selectedPart = part; d->m_selectedWidget = widget; if (part && !widget) { d->m_selectedWidget = part->widget(); } if (oldPart) { PartSelectEvent ev(false, oldPart, oldWidget); QApplication::sendEvent(oldPart, &ev); QApplication::sendEvent(oldWidget, &ev); } if (d->m_selectedPart) { PartSelectEvent ev(true, d->m_selectedPart, d->m_selectedWidget); QApplication::sendEvent(d->m_selectedPart, &ev); QApplication::sendEvent(d->m_selectedWidget, &ev); } } Part *PartManager::selectedPart() const { return d->m_selectedPart; } QWidget *PartManager::selectedWidget() const { return d->m_selectedWidget; } void PartManager::slotObjectDestroyed() { // qDebug(); removePart(const_cast(static_cast(sender()))); } void PartManager::slotWidgetDestroyed() { // qDebug(); if (static_cast(sender()) == d->m_activeWidget) { setActivePart(nullptr); //do not remove the part because if the part's widget dies, then the } //part will delete itself anyway, invoking removePart() in its destructor } const QList PartManager::parts() const { return d->m_parts; } void PartManager::addManagedTopLevelWidget(const QWidget *topLevel) { if (!topLevel->isTopLevel()) { return; } if (d->m_managedTopLevelWidgets.contains(topLevel)) { return; } d->m_managedTopLevelWidgets.append(topLevel); connect(topLevel, &QWidget::destroyed, this, &PartManager::slotManagedTopLevelWidgetDestroyed); } void PartManager::removeManagedTopLevelWidget(const QWidget *topLevel) { d->m_managedTopLevelWidgets.removeAll(topLevel); } void PartManager::slotManagedTopLevelWidgetDestroyed() { const QWidget *widget = static_cast(sender()); removeManagedTopLevelWidget(widget); } int PartManager::reason() const { return d->m_reason; } void PartManager::setIgnoreExplictFocusRequests(bool ignore) { d->m_bIgnoreExplicitFocusRequest = ignore; } diff --git a/src/plugin.cpp b/src/plugin.cpp index 7c823fe..0272fb6 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -1,293 +1,291 @@ /* This file is part of the KDE project Copyright (C) 1999 Simon Hausmann (C) 1999 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "plugin.h" #include "part.h" #include #include #include #include #include #include #include #include #include #include #include #include #include -#include - using namespace KParts; class Q_DECL_HIDDEN Plugin::PluginPrivate { public: QString m_parentInstance; QString m_library; // filename of the library }; Plugin::Plugin(QObject *parent) : QObject(parent), d(new PluginPrivate()) { //qDebug() << className(); } Plugin::~Plugin() { delete d; } QString Plugin::xmlFile() const { QString path = KXMLGUIClient::xmlFile(); if (d->m_parentInstance.isEmpty() || (!path.isEmpty() && QDir::isAbsolutePath(path))) { return path; } QString absPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, d->m_parentInstance + QLatin1Char('/') + path); Q_ASSERT(!absPath.isEmpty()); return absPath; } QString Plugin::localXMLFile() const { QString path = KXMLGUIClient::xmlFile(); if (d->m_parentInstance.isEmpty() || (!path.isEmpty() && QDir::isAbsolutePath(path))) { return path; } QString absPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + d->m_parentInstance + QLatin1Char('/') + path; return absPath; } //static QList Plugin::pluginInfos(const QString &componentName) { QList plugins; QMap sortedPlugins; const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, componentName + QLatin1String("/kpartplugins"), QStandardPaths::LocateDirectory); for (const QString &dir : dirs) { const auto rcfiles = QDir(dir).entryList(QStringList(QStringLiteral("*.rc"))); for (const QString &file : rcfiles) { const QFileInfo fInfo(dir + QLatin1Char('/') + file); QMap::Iterator mapIt = sortedPlugins.find(fInfo.fileName()); if (mapIt == sortedPlugins.end()) { mapIt = sortedPlugins.insert(fInfo.fileName(), QStringList()); } mapIt.value().append(fInfo.absoluteFilePath()); } } QMap::ConstIterator mapIt = sortedPlugins.constBegin(); QMap::ConstIterator mapEnd = sortedPlugins.constEnd(); for (; mapIt != mapEnd; ++mapIt) { PluginInfo info; QString doc; info.m_absXMLFileName = KXMLGUIClient::findMostRecentXMLFile(mapIt.value(), doc); if (info.m_absXMLFileName.isEmpty()) { continue; } // qDebug() << "found KParts Plugin : " << info.m_absXMLFileName; info.m_relXMLFileName = QLatin1String("kpartplugins/") + mapIt.key(); info.m_document.setContent(doc); if (info.m_document.documentElement().isNull()) { continue; } plugins.append(info); } return plugins; } void Plugin::loadPlugins(QObject *parent, const QString &componentName) { loadPlugins(parent, pluginInfos(componentName), componentName); } void Plugin::loadPlugins(QObject *parent, const QList &pluginInfos, const QString &componentName) { for (const auto &pluginInfo : pluginInfos) { const QString library = pluginInfo.m_document.documentElement().attribute(QStringLiteral("library")); if (library.isEmpty() || hasPlugin(parent, library)) { continue; } Plugin *plugin = loadPlugin(parent, library, pluginInfo.m_document.documentElement().attribute(QStringLiteral("X-KDE-PluginKeyword"))); if (plugin) { plugin->d->m_parentInstance = componentName; plugin->setXMLFile(pluginInfo.m_relXMLFileName, false, false); plugin->setDOMDocument(pluginInfo.m_document); } } } void Plugin::loadPlugins(QObject *parent, const QList &pluginInfos) { loadPlugins(parent, pluginInfos, QString()); } // static Plugin *Plugin::loadPlugin(QObject *parent, const QString &libname, const QString &keyword) { KPluginLoader loader(libname); KPluginFactory *factory = loader.factory(); if (!factory) { return nullptr; } Plugin *plugin = factory->create(keyword, parent); if (!plugin) { return nullptr; } plugin->d->m_library = libname; return plugin; } QList Plugin::pluginObjects(QObject *parent) { QList objects; if (!parent) { return objects; } objects = parent->findChildren(QString(), Qt::FindDirectChildrenOnly); return objects; } bool Plugin::hasPlugin(QObject *parent, const QString &library) { const QObjectList plugins = parent->children(); return std::any_of(plugins.begin(), plugins.end(), [&library](QObject *p) { Plugin *plugin = qobject_cast(p); return (plugin && plugin->d->m_library == library); }); } void Plugin::setComponentData(const KAboutData &pluginData) { KAboutData::registerPluginData(pluginData); KXMLGUIClient::setComponentName(pluginData.componentName(), pluginData.displayName()); } void Plugin::loadPlugins(QObject *parent, KXMLGUIClient *parentGUIClient, const QString &componentName, bool enableNewPluginsByDefault, int interfaceVersionRequired) { KConfigGroup cfgGroup(KSharedConfig::openConfig(componentName + QLatin1String("rc")), "KParts Plugins"); const QList plugins = pluginInfos(componentName); for (const auto &pluginInfo : plugins) { QDomElement docElem = pluginInfo.m_document.documentElement(); QString library = docElem.attribute(QStringLiteral("library")); QString keyword; if (library.isEmpty()) { continue; } // Check configuration const QString name = docElem.attribute(QStringLiteral("name")); bool pluginEnabled = enableNewPluginsByDefault; if (cfgGroup.hasKey(name + QLatin1String("Enabled"))) { pluginEnabled = cfgGroup.readEntry(name + QLatin1String("Enabled"), false); } else { // no user-setting, load plugin default setting QString relPath = componentName + QLatin1Char('/') + pluginInfo.m_relXMLFileName; relPath.truncate(relPath.lastIndexOf(QLatin1Char('.'))); // remove extension relPath += QLatin1String(".desktop"); //qDebug() << "looking for " << relPath; const QString desktopfile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, relPath); if (!desktopfile.isEmpty()) { //qDebug() << "loadPlugins found desktop file for " << name << ": " << desktopfile; KDesktopFile _desktop(desktopfile); const KConfigGroup desktop = _desktop.desktopGroup(); keyword = desktop.readEntry("X-KDE-PluginKeyword", ""); pluginEnabled = desktop.readEntry("X-KDE-PluginInfo-EnabledByDefault", enableNewPluginsByDefault); if (interfaceVersionRequired != 0) { const int version = desktop.readEntry("X-KDE-InterfaceVersion", 1); if (version != interfaceVersionRequired) { // qDebug() << "Discarding plugin " << name << ", interface version " << version << ", expected " << interfaceVersionRequired; pluginEnabled = false; } } } else { //qDebug() << "loadPlugins no desktop file found in " << relPath; } } // search through already present plugins const QObjectList pluginList = parent->children(); bool pluginFound = false; for (auto *p : pluginList) { Plugin *plugin = qobject_cast(p); if (plugin && plugin->d->m_library == library) { // delete and unload disabled plugins if (!pluginEnabled) { // qDebug() << "remove plugin " << name; KXMLGUIFactory *factory = plugin->factory(); if (factory) { factory->removeClient(plugin); } delete plugin; } pluginFound = true; break; } } // if the plugin is already loaded or if it's disabled in the // configuration do nothing if (pluginFound || !pluginEnabled) { continue; } // qDebug() << "load plugin " << name << " " << library << " " << keyword; Plugin *plugin = loadPlugin(parent, library, keyword); if (plugin) { plugin->d->m_parentInstance = componentName; plugin->setXMLFile(pluginInfo.m_relXMLFileName, false, false); plugin->setDOMDocument(pluginInfo.m_document); parentGUIClient->insertChildClient(plugin); } } } diff --git a/src/readonlypart.cpp b/src/readonlypart.cpp index 63ee003..fa54ea3 100644 --- a/src/readonlypart.cpp +++ b/src/readonlypart.cpp @@ -1,368 +1,367 @@ /* This file is part of the KDE project Copyright (C) 1999 Simon Hausmann (C) 1999-2005 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "readonlypart.h" #include "readonlypart_p.h" +#include "kparts_logging.h" + #include "browserextension.h" #include "guiactivateevent.h" #include #include #include #include #include #include #include -#include - - using namespace KParts; ReadOnlyPart::ReadOnlyPart(QObject *parent) : Part(*new ReadOnlyPartPrivate(this), parent) { } ReadOnlyPart::ReadOnlyPart(ReadOnlyPartPrivate &dd, QObject *parent) : Part(dd, parent) { } ReadOnlyPart::~ReadOnlyPart() { ReadOnlyPart::closeUrl(); } QUrl ReadOnlyPart::url() const { Q_D(const ReadOnlyPart); return d->m_url; } void ReadOnlyPart::setUrl(const QUrl &url) { Q_D(ReadOnlyPart); if (d->m_url != url) { d->m_url = url; emit urlChanged(url); } } QString ReadOnlyPart::localFilePath() const { Q_D(const ReadOnlyPart); return d->m_file; } void ReadOnlyPart::setLocalFilePath(const QString &localFilePath) { Q_D(ReadOnlyPart); d->m_file = localFilePath; } #if KPARTS_BUILD_DEPRECATED_SINCE(5, 0) bool ReadOnlyPart::isLocalFileTemporary() const { Q_D(const ReadOnlyPart); return d->m_bTemp; } #endif #if KPARTS_BUILD_DEPRECATED_SINCE(5, 0) void ReadOnlyPart::setLocalFileTemporary(bool temp) { Q_D(ReadOnlyPart); d->m_bTemp = temp; } #endif void ReadOnlyPart::setProgressInfoEnabled(bool show) { Q_D(ReadOnlyPart); d->m_showProgressInfo = show; } bool ReadOnlyPart::isProgressInfoEnabled() const { Q_D(const ReadOnlyPart); return d->m_showProgressInfo; } #if KPARTS_BUILD_DEPRECATED_SINCE(3, 0) void ReadOnlyPart::showProgressInfo(bool show) { Q_D(ReadOnlyPart); d->m_showProgressInfo = show; } #endif bool ReadOnlyPart::openUrl(const QUrl &url) { Q_D(ReadOnlyPart); if (!url.isValid()) { return false; } if (d->m_bAutoDetectedMime) { d->m_arguments.setMimeType(QString()); d->m_bAutoDetectedMime = false; } OpenUrlArguments args = d->m_arguments; d->m_closeUrlFromOpenUrl = true; const bool closed = closeUrl(); d->m_closeUrlFromOpenUrl = false; if (!closed) { return false; } d->m_arguments = args; setUrl(url); d->m_file.clear(); if (d->m_url.isLocalFile()) { d->m_file = d->m_url.toLocalFile(); return d->openLocalFile(); } else if (KProtocolInfo::protocolClass(url.scheme()) == QLatin1String(":local")) { // Maybe we can use a "local path", to avoid a temp copy? KIO::JobFlags flags = d->m_showProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo; d->m_statJob = KIO::mostLocalUrl(d->m_url, flags); KJobWidgets::setWindow(d->m_statJob, widget()); connect(d->m_statJob, SIGNAL(result(KJob*)), this, SLOT(_k_slotStatJobFinished(KJob*))); return true; } else { d->openRemoteFile(); return true; } } bool ReadOnlyPart::openFile() { - qWarning() << "Default implementation of ReadOnlyPart::openFile called!" - << metaObject()->className() << "should reimplement either openUrl or openFile."; + qCWarning(KPARTSLOG) << "Default implementation of ReadOnlyPart::openFile called!" + << metaObject()->className() << "should reimplement either openUrl or openFile."; return false; } bool ReadOnlyPartPrivate::openLocalFile() { Q_Q(ReadOnlyPart); emit q->started(nullptr); m_bTemp = false; // set the mimetype only if it was not already set (for example, by the host application) if (m_arguments.mimeType().isEmpty()) { // get the mimetype of the file // using findByUrl() to avoid another string -> url conversion QMimeDatabase db; QMimeType mime = db.mimeTypeForUrl(m_url); if (!mime.isDefault()) { m_arguments.setMimeType(mime.name()); m_bAutoDetectedMime = true; } } const bool ret = q->openFile(); if (ret) { emit q->setWindowCaption(m_url.toDisplayString()); emit q->completed(); } else { emit q->canceled(QString()); } return ret; } void ReadOnlyPartPrivate::openRemoteFile() { Q_Q(ReadOnlyPart); m_bTemp = true; // Use same extension as remote file. This is important for mimetype-determination (e.g. koffice) QString fileName = m_url.fileName(); QFileInfo fileInfo(fileName); QString ext = fileInfo.completeSuffix(); QString extension; if (!ext.isEmpty() && !m_url.hasQuery()) { // not if the URL has a query, e.g. cgi.pl?something extension = QLatin1Char('.') + ext; // keep the '.' } QTemporaryFile tempFile(QDir::tempPath() + QLatin1Char('/') + q->componentData().componentName() + QLatin1String("XXXXXX") + extension); tempFile.setAutoRemove(false); tempFile.open(); m_file = tempFile.fileName(); QUrl destURL = QUrl::fromLocalFile(m_file); KIO::JobFlags flags = m_showProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo; flags |= KIO::Overwrite; m_job = KIO::file_copy(m_url, destURL, 0600, flags); KJobWidgets::setWindow(m_job, q->widget()); emit q->started(m_job); QObject::connect(m_job, SIGNAL(result(KJob*)), q, SLOT(_k_slotJobFinished(KJob*))); QObject::connect(m_job, SIGNAL(mimetype(KIO::Job*,QString)), q, SLOT(_k_slotGotMimeType(KIO::Job*,QString))); } void ReadOnlyPart::abortLoad() { Q_D(ReadOnlyPart); if (d->m_statJob) { //qDebug() << "Aborting job" << d->m_statJob; d->m_statJob->kill(); d->m_statJob = nullptr; } if (d->m_job) { //qDebug() << "Aborting job" << d->m_job; d->m_job->kill(); d->m_job = nullptr; } } bool ReadOnlyPart::closeUrl() { Q_D(ReadOnlyPart); abortLoad(); //just in case d->m_arguments = KParts::OpenUrlArguments(); if (!d->m_closeUrlFromOpenUrl) { setUrl(QUrl()); } if (d->m_bTemp) { QFile::remove(d->m_file); d->m_bTemp = false; } // It always succeeds for a read-only part, // but the return value exists for reimplementations // (e.g. pressing cancel for a modified read-write part) return true; } void ReadOnlyPartPrivate::_k_slotStatJobFinished(KJob *job) { Q_ASSERT(job == m_statJob); m_statJob = nullptr; // We could emit canceled on error, but we haven't even emitted started yet, // this could maybe confuse some apps? So for now we'll just fallback to KIO::get // and error again. Well, maybe this even helps with wrong stat results. if (!job->error()) { const QUrl localUrl = static_cast(job)->mostLocalUrl(); if (localUrl.isLocalFile()) { m_file = localUrl.toLocalFile(); (void)openLocalFile(); return; } } openRemoteFile(); } void ReadOnlyPartPrivate::_k_slotJobFinished(KJob *job) { Q_Q(ReadOnlyPart); Q_ASSERT(job == m_job); m_job = nullptr; if (job->error()) { emit q->canceled(job->errorString()); } else { if (q->openFile()) { emit q->setWindowCaption(m_url.toDisplayString()); emit q->completed(); } else { emit q->canceled(QString()); } } } void ReadOnlyPartPrivate::_k_slotGotMimeType(KIO::Job *job, const QString &mime) { // qDebug() << mime; Q_ASSERT(job == m_job); Q_UNUSED(job) // set the mimetype only if it was not already set (for example, by the host application) if (m_arguments.mimeType().isEmpty()) { m_arguments.setMimeType(mime); m_bAutoDetectedMime = true; } } void ReadOnlyPart::guiActivateEvent(GUIActivateEvent *event) { Q_D(ReadOnlyPart); if (event->activated()) { if (!d->m_url.isEmpty()) { // qDebug() << d->m_url; emit setWindowCaption(d->m_url.toDisplayString()); } else { emit setWindowCaption(QString()); } } } bool ReadOnlyPart::openStream(const QString &mimeType, const QUrl &url) { Q_D(ReadOnlyPart); OpenUrlArguments args = d->m_arguments; if (!closeUrl()) { return false; } d->m_arguments = args; setUrl(url); return doOpenStream(mimeType); } bool ReadOnlyPart::writeStream(const QByteArray &data) { return doWriteStream(data); } bool ReadOnlyPart::closeStream() { return doCloseStream(); } BrowserExtension *ReadOnlyPart::browserExtension() const { return findChild(); } void KParts::ReadOnlyPart::setArguments(const OpenUrlArguments &arguments) { Q_D(ReadOnlyPart); d->m_arguments = arguments; d->m_bAutoDetectedMime = arguments.mimeType().isEmpty(); } OpenUrlArguments KParts::ReadOnlyPart::arguments() const { Q_D(const ReadOnlyPart); return d->m_arguments; } #include "moc_readonlypart.cpp" diff --git a/src/readwritepart.cpp b/src/readwritepart.cpp index fb36ae5..98a5b85 100644 --- a/src/readwritepart.cpp +++ b/src/readwritepart.cpp @@ -1,329 +1,329 @@ /* This file is part of the KDE project Copyright (C) 1999 Simon Hausmann (C) 1999-2005 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "readwritepart.h" #include "readwritepart_p.h" +#include "kparts_logging.h" + #include #include #include #include #include #include #include #include -#include - #include #ifdef Q_OS_WIN #include //CreateHardLink() #endif using namespace KParts; ReadWritePart::ReadWritePart(QObject *parent) : ReadOnlyPart(*new ReadWritePartPrivate(this), parent) { } ReadWritePart::~ReadWritePart() { // parent destructor will delete temp file // we can't call our own closeUrl() here, because // "cancel" wouldn't cancel anything. We have to assume // the app called closeUrl() before destroying us. } void ReadWritePart::setReadWrite(bool readwrite) { Q_D(ReadWritePart); // Perhaps we should check isModified here and issue a warning if true d->m_bReadWrite = readwrite; } void ReadWritePart::setModified(bool modified) { Q_D(ReadWritePart); // qDebug() << "setModified(" << (modified ? "true" : "false") << ")"; if (!d->m_bReadWrite && modified) { - qCritical() << "Can't set a read-only document to 'modified' !"; + qCCritical(KPARTSLOG) << "Can't set a read-only document to 'modified' !"; return; } d->m_bModified = modified; } void ReadWritePart::setModified() { setModified(true); } bool ReadWritePart::queryClose() { Q_D(ReadWritePart); if (!isReadWrite() || !isModified()) { return true; } QString docName = url().fileName(); if (docName.isEmpty()) { docName = i18n("Untitled"); } QWidget *parentWidget = widget(); if (!parentWidget) { parentWidget = QApplication::activeWindow(); } int res = KMessageBox::warningYesNoCancel(parentWidget, i18n("The document \"%1\" has been modified.\n" "Do you want to save your changes or discard them?", docName), i18n("Close Document"), KStandardGuiItem::save(), KStandardGuiItem::discard()); bool abortClose = false; bool handled = false; switch (res) { case KMessageBox::Yes : emit sigQueryClose(&handled, &abortClose); if (!handled) { if (d->m_url.isEmpty()) { QUrl url = QFileDialog::getSaveFileUrl(parentWidget); if (url.isEmpty()) { return false; } saveAs(url); } else { save(); } } else if (abortClose) { return false; } return waitSaveComplete(); case KMessageBox::No : return true; default : // case KMessageBox::Cancel : return false; } } bool ReadWritePart::closeUrl() { abortLoad(); //just in case if (isReadWrite() && isModified()) { if (!queryClose()) { return false; } } // Not modified => ok and delete temp file. return ReadOnlyPart::closeUrl(); } bool ReadWritePart::closeUrl(bool promptToSave) { return promptToSave ? closeUrl() : ReadOnlyPart::closeUrl(); } bool ReadWritePart::save() { Q_D(ReadWritePart); d->m_saveOk = false; if (d->m_file.isEmpty()) { // document was created empty d->prepareSaving(); } if (saveFile()) { return saveToUrl(); } else { emit canceled(QString()); } return false; } bool ReadWritePart::saveAs(const QUrl &url) { Q_D(ReadWritePart); if (!url.isValid()) { - qCritical() << "saveAs: Malformed URL " << url; + qCCritical(KPARTSLOG) << "saveAs: Malformed URL" << url; return false; } d->m_duringSaveAs = true; d->m_originalURL = d->m_url; d->m_originalFilePath = d->m_file; d->m_url = url; // Store where to upload in saveToURL d->prepareSaving(); bool result = save(); // Save local file and upload local file if (result) { if (d->m_originalURL != d->m_url) { emit urlChanged(d->m_url); } emit setWindowCaption(d->m_url.toDisplayString()); } else { d->m_url = d->m_originalURL; d->m_file = d->m_originalFilePath; d->m_duringSaveAs = false; d->m_originalURL = QUrl(); d->m_originalFilePath.clear(); } return result; } // Set m_file correctly for m_url void ReadWritePartPrivate::prepareSaving() { // Local file if (m_url.isLocalFile()) { if (m_bTemp) { // get rid of a possible temp file first // (happens if previous url was remote) QFile::remove(m_file); m_bTemp = false; } m_file = m_url.toLocalFile(); } else { // Remote file // We haven't saved yet, or we did but locally - provide a temp file if (m_file.isEmpty() || !m_bTemp) { QTemporaryFile tempFile; tempFile.setAutoRemove(false); tempFile.open(); m_file = tempFile.fileName(); m_bTemp = true; } // otherwise, we already had a temp file } } static inline bool makeHardLink(const QString& src, const QString& dest) { #ifndef Q_OS_WIN return ::link(QFile::encodeName(src).constData(), QFile::encodeName(dest).constData()) == 0; #else return CreateHardLinkW((LPCWSTR)dest.utf16(), (LPCWSTR)src.utf16(), nullptr) != 0; #endif } bool ReadWritePart::saveToUrl() { Q_D(ReadWritePart); if (d->m_url.isLocalFile()) { setModified(false); emit completed(); // if m_url is a local file there won't be a temp file -> nothing to remove Q_ASSERT(!d->m_bTemp); d->m_saveOk = true; d->m_duringSaveAs = false; d->m_originalURL = QUrl(); d->m_originalFilePath.clear(); return true; // Nothing to do } else { if (d->m_uploadJob) { QFile::remove(d->m_uploadJob->srcUrl().toLocalFile()); d->m_uploadJob->kill(); d->m_uploadJob = nullptr; } QTemporaryFile *tempFile = new QTemporaryFile(); tempFile->open(); QString uploadFile = tempFile->fileName(); delete tempFile; QUrl uploadUrl = QUrl::fromLocalFile(uploadFile); // Create hardlink if (!makeHardLink(d->m_file, uploadFile)) { // Uh oh, some error happened. return false; } d->m_uploadJob = KIO::file_move(uploadUrl, d->m_url, -1, KIO::Overwrite); KJobWidgets::setWindow(d->m_uploadJob, widget()); connect(d->m_uploadJob, SIGNAL(result(KJob*)), this, SLOT(_k_slotUploadFinished(KJob*))); return true; } } void ReadWritePartPrivate::_k_slotUploadFinished(KJob *) { Q_Q(ReadWritePart); if (m_uploadJob->error()) { QFile::remove(m_uploadJob->srcUrl().toLocalFile()); QString error = m_uploadJob->errorString(); m_uploadJob = nullptr; if (m_duringSaveAs) { q->setUrl(m_originalURL); m_file = m_originalFilePath; } emit q->canceled(error); } else { ::org::kde::KDirNotify::emitFilesAdded(m_url.adjusted(QUrl::RemoveFilename)); m_uploadJob = nullptr; q->setModified(false); emit q->completed(); m_saveOk = true; } m_duringSaveAs = false; m_originalURL = QUrl(); m_originalFilePath.clear(); if (m_waitForSave) { m_eventLoop.quit(); } } bool ReadWritePart::isReadWrite() const { Q_D(const ReadWritePart); return d->m_bReadWrite; } bool ReadWritePart::isModified() const { Q_D(const ReadWritePart); return d->m_bModified; } bool ReadWritePart::waitSaveComplete() { Q_D(ReadWritePart); if (!d->m_uploadJob) { return d->m_saveOk; } d->m_waitForSave = true; d->m_eventLoop.exec(QEventLoop::ExcludeUserInputEvents); d->m_waitForSave = false; return d->m_saveOk; } #include "moc_readwritepart.cpp" diff --git a/src/scriptableextension.cpp b/src/scriptableextension.cpp index 353ac14..69b182e 100644 --- a/src/scriptableextension.cpp +++ b/src/scriptableextension.cpp @@ -1,464 +1,462 @@ /* This file is part of the KDE project Copyright (C) 2010 Maksim Orlovich Copyright (C) 2002, 2004 Koos Vriezen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "scriptableextension.h" #include "scriptableextension_p.h" -#include - namespace KParts { struct ScriptableExtensionPrivate { ScriptableExtension *hostContext; ScriptableExtensionPrivate(): hostContext(nullptr) {} }; ScriptableExtension::ScriptableExtension(QObject *parent): QObject(parent), d(new ScriptableExtensionPrivate) {} ScriptableExtension::~ScriptableExtension() { delete d; } ScriptableExtension *ScriptableExtension::childObject(QObject *obj) { return obj->findChild(QString(), Qt::FindDirectChildrenOnly); } ScriptableExtension *ScriptableExtension::adapterFromLiveConnect(QObject *parentObj, LiveConnectExtension *oldApi) { return new ScriptableLiveConnectExtension(parentObj, oldApi); } void ScriptableExtension::setHost(ScriptableExtension *host) { d->hostContext = host; } ScriptableExtension *ScriptableExtension::host() const { return d->hostContext; } QVariant ScriptableExtension::rootObject() { return QVariant::fromValue(Null()); } QVariant ScriptableExtension::enclosingObject() { if (d->hostContext) { return d->hostContext->encloserForKid(this); } else { return QVariant::fromValue(Null()); } } QVariant ScriptableExtension::encloserForKid(KParts::ScriptableExtension *kid) { Q_UNUSED(kid); return QVariant::fromValue(Null()); } static QVariant unimplemented() { ScriptableExtension::Exception except(QStringLiteral("[unimplemented]")); return QVariant::fromValue(except); } QVariant ScriptableExtension::callAsFunction(ScriptableExtension *callerPrincipal, quint64 objId, const ArgList &args) { Q_UNUSED(callerPrincipal); Q_UNUSED(objId); Q_UNUSED(args); return unimplemented(); } QVariant ScriptableExtension::callFunctionReference(ScriptableExtension *callerPrincipal, quint64 objId, const QString &f, const ArgList &args) { Q_UNUSED(callerPrincipal); Q_UNUSED(objId); Q_UNUSED(args); Q_UNUSED(f); return unimplemented(); } QVariant ScriptableExtension::callAsConstructor(ScriptableExtension *callerPrincipal, quint64 objId, const ArgList &args) { Q_UNUSED(callerPrincipal); Q_UNUSED(objId); Q_UNUSED(args); return unimplemented(); } bool ScriptableExtension::hasProperty(ScriptableExtension *callerPrincipal, quint64 objId, const QString &propName) { Q_UNUSED(callerPrincipal); Q_UNUSED(objId); Q_UNUSED(propName); return false; } QVariant ScriptableExtension::get(ScriptableExtension *callerPrincipal, quint64 objId, const QString &propName) { Q_UNUSED(callerPrincipal); Q_UNUSED(objId); Q_UNUSED(propName); return unimplemented(); } bool ScriptableExtension::put(ScriptableExtension *callerPrincipal, quint64 objId, const QString &propName, const QVariant &value) { Q_UNUSED(callerPrincipal); Q_UNUSED(objId); Q_UNUSED(propName); Q_UNUSED(value); return false; } bool ScriptableExtension::removeProperty(ScriptableExtension *callerPrincipal, quint64 objId, const QString &propName) { Q_UNUSED(callerPrincipal); Q_UNUSED(objId); Q_UNUSED(propName); return false; } bool ScriptableExtension::enumerateProperties(ScriptableExtension *callerPrincipal, quint64 objId, QStringList *result) { Q_UNUSED(callerPrincipal); Q_UNUSED(objId); Q_UNUSED(result); return false; } bool ScriptableExtension::setException(ScriptableExtension *callerPrincipal, const QString &message) { Q_UNUSED(callerPrincipal); Q_UNUSED(message); return false; } QVariant ScriptableExtension::evaluateScript(ScriptableExtension *callerPrincipal, quint64 contextObjectId, const QString &code, ScriptLanguage language) { Q_UNUSED(callerPrincipal); Q_UNUSED(contextObjectId); Q_UNUSED(code); Q_UNUSED(language); return unimplemented(); } bool ScriptableExtension::isScriptLanguageSupported(ScriptLanguage lang) const { Q_UNUSED(lang); return false; } void ScriptableExtension::acquire(quint64 objId) { Q_UNUSED(objId); } QVariant ScriptableExtension::acquireValue(const QVariant &v) { if (v.canConvert()) { Object o = v.value(); o.owner->acquire(o.objId); } else if (v.canConvert()) { FunctionRef fr = v.value(); fr.base.owner->acquire(fr.base.objId); } return v; } void ScriptableExtension::release(quint64 objId) { Q_UNUSED(objId); } QVariant ScriptableExtension::releaseValue(const QVariant &v) { if (v.canConvert()) { Object o = v.value(); o.owner->release(o.objId); } else if (v.canConvert()) { FunctionRef fr = v.value(); fr.base.owner->release(fr.base.objId); } return v; } // LiveConnectExtension -> ScriptableExtension adapter. We use // lc object IDs as our own object IDs. // ---------------------------------------------------------------------------- ScriptableLiveConnectExtension::ScriptableLiveConnectExtension(QObject *p, LiveConnectExtension *old): ScriptableExtension(p), wrapee(old) { connect(wrapee, &LiveConnectExtension::partEvent, this, &ScriptableLiveConnectExtension::liveConnectEvent); } QVariant ScriptableLiveConnectExtension::rootObject() { // Plugin root is always LC object #0. return acquireValue(QVariant::fromValue(ScriptableExtension::Object(this, 0))); } bool ScriptableLiveConnectExtension::hasProperty(ScriptableExtension *, quint64 objId, const QString &propName) { QVariant val = get(nullptr, objId, propName); bool ok = !val.canConvert(); releaseValue(val); return ok; } // Note that since we wrap around a plugin, and do not implement the browser, // we do not perform XSS checks ourselves. QVariant ScriptableLiveConnectExtension::callFunctionReference(ScriptableExtension *, quint64 o, const QString &f, const ScriptableExtension::ArgList &a) { QStringList qargs; // Convert args to strings for LC use. qargs.reserve(a.size()); for (const auto &arg : a) { bool ok; qargs.append(toLC(arg, &ok)); if (!ok) { return unimplemented(); } } LiveConnectExtension::Type retType; unsigned long retObjId; QString retVal; if (wrapee->call((unsigned long)o, f, qargs, retType, retObjId, retVal)) { return acquireValue(fromLC(QString(), retType, retObjId, retVal)); } else { return unimplemented(); } } QVariant ScriptableLiveConnectExtension::get(ScriptableExtension *, quint64 objId, const QString &propName) { LiveConnectExtension::Type retType; unsigned long retObjId; QString retVal; if (wrapee->get((unsigned long)objId, propName, retType, retObjId, retVal)) { return acquireValue(fromLC(propName, retType, retObjId, retVal)); } else { // exception signals failure. ### inellegant return unimplemented(); } } bool ScriptableLiveConnectExtension::put(ScriptableExtension *, quint64 objId, const QString &propName, const QVariant &value) { bool ok; QString val = toLC(value, &ok); if (!ok) { return false; } return wrapee->put((unsigned long)objId, propName, val); } QVariant ScriptableLiveConnectExtension::fromLC(const QString &name, LiveConnectExtension::Type type, unsigned long objId, const QString &value) { switch (type) { case KParts::LiveConnectExtension::TypeBool: { bool ok; int i = value.toInt(&ok); if (ok) { return QVariant(bool(i)); } return QVariant(value.toLower() == QLatin1String("true")); } case KParts::LiveConnectExtension::TypeObject: case KParts::LiveConnectExtension::TypeFunction: { if (!refCounts.contains(objId)) { refCounts[objId] = 0; } Object o = ScriptableExtension::Object(this, objId); if (type == KParts::LiveConnectExtension::TypeObject) { return QVariant::fromValue(o); } else { return QVariant::fromValue(FunctionRef(o, name)); } } case KParts::LiveConnectExtension::TypeNumber: return QVariant(value.toDouble()); case KParts::LiveConnectExtension::TypeString: return QVariant(value); case KParts::LiveConnectExtension::TypeVoid: default: return QVariant::fromValue(ScriptableExtension::Undefined()); } } QString ScriptableLiveConnectExtension::toLC(const QVariant &in, bool *ok) { *ok = true; // most of the time. // Objects (or exceptions) can't be converted if (in.canConvert() || in.canConvert() || in.canConvert()) { *ok = false; return QString(); } // Convert null and undefined to appropriate strings // ### this matches old KHTML behavior, but is this sensible? if (in.canConvert()) { return QStringLiteral("null"); } if (in.canConvert()) { return QStringLiteral("undefined"); } if (in.type() == QVariant::Bool) { return in.toBool() ? QStringLiteral("true") : QStringLiteral("false"); } // Just stringify everything else, makes sense for nums as well. if (in.canConvert()) { return in.toString(); } // something really icky... *ok = false; return QString(); } void ScriptableLiveConnectExtension::acquire(quint64 objId) { ++refCounts[objId]; } void ScriptableLiveConnectExtension::release(quint64 objId) { int newRC = --refCounts[objId]; if (!newRC) { if (objId != 0) { wrapee->unregister((unsigned long)objId); } refCounts.remove(objId); } } void ScriptableLiveConnectExtension::liveConnectEvent(const unsigned long, const QString &event, const LiveConnectExtension::ArgList &args) { // We want to evaluate in the enclosure's context. QVariant enclosure = enclosingObject(); if (!enclosure.canConvert()) { releaseValue(enclosure); // qDebug() << "No enclosure, can't evaluate"; return; } Object enclosureObj = enclosure.value(); if (!host()->isScriptLanguageSupported(ECMAScript)) { releaseValue(enclosure); // qDebug() << "Host can't evaluate ECMAScript"; } // Compute a string to evaluate. We need to escape a lot of stuff // since we're composing a bunch of strings into one. QString script = event + QLatin1Char('('); LiveConnectExtension::ArgList::const_iterator i = args.begin(); const LiveConnectExtension::ArgList::const_iterator argsBegin = i; const LiveConnectExtension::ArgList::const_iterator argsEnd = args.end(); for (; i != argsEnd; ++i) { if (i != argsBegin) { script += QLatin1Char(','); } if ((*i).first == KParts::LiveConnectExtension::TypeString) { script += QLatin1Char('"') + QString((*i).second).replace(QLatin1Char('\\'), QLatin1String("\\\\")).replace(QLatin1Char('"'), QLatin1String("\\\"")) + QLatin1Char('"'); } else { script += (*i).second; } } script += QLatin1Char(')'); // qDebug() << script; // Ask host to evaluate.. (unfortunately, we can't do anything with the result, // but anything that uses this interface isn't expective one in the first place) QVariant result = host()->evaluateScript(this, enclosureObj.objId, script); releaseValue(result); releaseValue(enclosure); } // hash functions // ---------------------------------------------------------------------------- unsigned int qHash(const KParts::ScriptableExtension::Object &o, uint seed) { return qHash(qMakePair(o.owner, o.objId), seed); } unsigned int qHash(const KParts::ScriptableExtension::FunctionRef &f) { return qHash(qMakePair(f.base, f.field)); } } // namespace KParts #include "moc_scriptableextension.cpp" #include "moc_scriptableextension_p.cpp" diff --git a/src/statusbarextension.cpp b/src/statusbarextension.cpp index 7d382c5..e6425a9 100644 --- a/src/statusbarextension.cpp +++ b/src/statusbarextension.cpp @@ -1,201 +1,200 @@ /* This file is part of the KDE project Copyright (C) 2003 Daniel Molkentin Copyright (C) 2003 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "statusbarextension.h" +#include "kparts_logging.h" + #include "readonlypart.h" #include "guiactivateevent.h" #include #include #include #include -#include - using namespace KParts; /////////////////////////////////////////////////////////////////// // Helper Classes /////////////////////////////////////////////////////////////////// class KParts::StatusBarItem { public: StatusBarItem() // for QValueList : m_widget(nullptr), m_visible(false) {} StatusBarItem(QWidget *widget, int stretch, bool permanent) : m_widget(widget), m_stretch(stretch), m_permanent(permanent), m_visible(false) {} QWidget *widget() const { return m_widget; } void ensureItemShown(QStatusBar *sb) { if (m_widget && !m_visible) { if (m_permanent) { sb->addPermanentWidget(m_widget, m_stretch); } else { sb->addWidget(m_widget, m_stretch); } m_visible = true; m_widget->show(); } } void ensureItemHidden(QStatusBar *sb) { if (m_widget && m_visible) { sb->removeWidget(m_widget); m_visible = false; m_widget->hide(); } } private: QPointer m_widget; int m_stretch; bool m_permanent; bool m_visible; // true when the item has been added to the statusbar }; class KParts::StatusBarExtensionPrivate { public: StatusBarExtensionPrivate(StatusBarExtension *q): q(q), m_statusBar(nullptr), m_activated(true) {} StatusBarExtension *q; QList m_statusBarItems; // Our statusbar items QStatusBar *m_statusBar; bool m_activated; }; /////////////////////////////////////////////////////////////////// StatusBarExtension::StatusBarExtension(KParts::Part *parent) : QObject(parent), d(new StatusBarExtensionPrivate(this)) { parent->installEventFilter(this); } StatusBarExtension::StatusBarExtension(KParts::ReadOnlyPart *parent) : QObject(parent), d(new StatusBarExtensionPrivate(this)) { parent->installEventFilter(this); } StatusBarExtension::~StatusBarExtension() { QStatusBar *sb = d->m_statusBar; for (int i = d->m_statusBarItems.count() - 1; i >= 0; --i) { if (d->m_statusBarItems[i].widget()) { if (sb) { d->m_statusBarItems[i].ensureItemHidden(sb); } d->m_statusBarItems[i].widget()->deleteLater(); } } delete d; } StatusBarExtension *StatusBarExtension::childObject(QObject *obj) { return obj->findChild(QString(), Qt::FindDirectChildrenOnly); } bool StatusBarExtension::eventFilter(QObject *watched, QEvent *ev) { if (!GUIActivateEvent::test(ev) || !::qobject_cast(watched)) { return QObject::eventFilter(watched, ev); } QStatusBar *sb = statusBar(); if (!sb) { return QObject::eventFilter(watched, ev); } GUIActivateEvent *gae = static_cast(ev); d->m_activated = gae->activated(); if (d->m_activated) { for (auto &item : d->m_statusBarItems) { item.ensureItemShown(sb); } } else { for (auto &item : d->m_statusBarItems) { item.ensureItemHidden(sb); } } return false; } QStatusBar *StatusBarExtension::statusBar() const { if (!d->m_statusBar) { KParts::Part *part = qobject_cast(parent()); QWidget *w = part ? part->widget() : nullptr; KMainWindow *mw = w ? qobject_cast(w->topLevelWidget()) : nullptr; if (mw) { d->m_statusBar = mw->statusBar(); } } return d->m_statusBar; } void StatusBarExtension::setStatusBar(QStatusBar *status) { d->m_statusBar = status; } void StatusBarExtension::addStatusBarItem(QWidget *widget, int stretch, bool permanent) { d->m_statusBarItems.append(StatusBarItem(widget, stretch, permanent)); StatusBarItem &it = d->m_statusBarItems.last(); QStatusBar *sb = statusBar(); if (sb && d->m_activated) { it.ensureItemShown(sb); } } void StatusBarExtension::removeStatusBarItem(QWidget *widget) { QStatusBar *sb = statusBar(); QList::iterator it = d->m_statusBarItems.begin(); for (; it != d->m_statusBarItems.end(); ++it) if ((*it).widget() == widget) { if (sb) { (*it).ensureItemHidden(sb); } d->m_statusBarItems.erase(it); return; } - qWarning() << "StatusBarExtension::removeStatusBarItem. Widget not found : " << widget; + qCWarning(KPARTSLOG) << "StatusBarExtension::removeStatusBarItem. Widget not found :" << widget; } -