diff --git a/src/core/engine.cpp b/src/core/engine.cpp --- a/src/core/engine.cpp +++ b/src/core/engine.cpp @@ -94,6 +94,7 @@ connect(m_searchTimer, &QTimer::timeout, this, &Engine::slotSearchTimerExpired); connect(m_installation, &Installation::signalInstallationFinished, this, &Engine::slotInstallationFinished); connect(m_installation, &Installation::signalInstallationFailed, this, &Engine::slotInstallationFailed); + connect(m_installation, &Installation::signalInstallationError, this, [this](const QString &message){ emit signalErrorCode(ErrorCode::InstallationError, i18n("An error occurred during the installation process:\n%1", message), QVariant()); }); // Pass along old error signal through new signal for locations which have not been updated yet connect(this, &Engine::signalError, this, [this](const QString& message){ emit signalErrorCode(ErrorCode::UnknownError, message, QVariant()); }); } diff --git a/src/core/installation.h b/src/core/installation.h --- a/src/core/installation.h +++ b/src/core/installation.h @@ -135,6 +135,12 @@ void signalEntryChanged(const KNSCore::EntryInternal &entry); void signalInstallationFinished(); void signalInstallationFailed(const QString &message); + /** + * An informational signal fired when a serious error occurs during the installation. + * @param message The description of the error (a message intended to be human readable) + * @since 5.69 + */ + void signalInstallationError(const QString &message); void signalPayloadLoaded(QUrl payload); // FIXME: return Entry diff --git a/src/core/installation.cpp b/src/core/installation.cpp --- a/src/core/installation.cpp +++ b/src/core/installation.cpp @@ -448,6 +448,7 @@ } else { qCCritical(KNEWSTUFFCORE) << "Could not determine type of archive file '" << payloadfile << "'"; if (uncompression == QLatin1String("always")) { + emit signalInstallationError(i18n("Could not determine the type of archive of the downloaded file %1", payloadfile)); return QStringList(); } isarchive = false; @@ -458,6 +459,7 @@ if (!success) { qCCritical(KNEWSTUFFCORE) << "Cannot open archive file '" << payloadfile << "'"; if (uncompression == QLatin1String("always")) { + emit signalInstallationError(i18n("Failed to open the archive file %1. The reported error was: %2", payloadfile, archive->errorString())); return QStringList(); } // otherwise, just copy the file @@ -553,6 +555,7 @@ qCDebug(KNEWSTUFFCORE) << "move: " << file.fileName() << " to " << installpath; } if (!success) { + emit signalInstallationError(i18n("Unable to mode the file %1 to the intended destination %2", payloadfile, installpath)); qCCritical(KNEWSTUFFCORE) << "Cannot move file '" << payloadfile << "' to destination '" << installpath << "'"; return QStringList(); } @@ -571,10 +574,13 @@ qCDebug(KNEWSTUFFCORE) << "Run command: " << command; QProcess* ret = new QProcess(this); - connect(ret, static_cast(&QProcess::finished), this, [this, command](int exitcode, QProcess::ExitStatus status) { + connect(ret, static_cast(&QProcess::finished), this, [this, command, ret](int exitcode, QProcess::ExitStatus status) { + const QString output{QString::fromLocal8Bit(ret->readAll())}; if (status == QProcess::CrashExit) { + emit signalInstallationError(i18n("The installation failed while attempting to run the command:\n%1\nThe returned output was:\n%2", command, output)); qCCritical(KNEWSTUFFCORE) << "Process crashed with command: " << command; } else if (exitcode) { + emit signalInstallationError(i18n("The installation failed while with code %1 while attempting to run the command:\n%2\nThe returned output was:\n%3", exitcode, command, output)); qCCritical(KNEWSTUFFCORE) << "Command '" << command << "' failed with code" << exitcode; } sender()->deleteLater(); @@ -603,6 +609,7 @@ int exitcode = QProcess::execute(command, QStringList()); if (exitcode) { + emit signalInstallationError(i18n("The uninstallation process failed to successfully run the command %1", command)); qCCritical(KNEWSTUFFCORE) << "Command failed" << command; } else { qCDebug(KNEWSTUFFCORE) << "Command executed successfully: " << command; diff --git a/src/qtquick/qml/EntryDetails.qml b/src/qtquick/qml/EntryDetails.qml --- a/src/qtquick/qml/EntryDetails.qml +++ b/src/qtquick/qml/EntryDetails.qml @@ -64,6 +64,7 @@ newStuffModel.installItem(entryId, downloadItemId); } } + Private.ErrorDisplayer { engine: component.newStuffModel.engine; active: component.isCurrentPage; } Connections { target: newStuffModel diff --git a/src/qtquick/qml/Page.qml b/src/qtquick/qml/Page.qml --- a/src/qtquick/qml/Page.qml +++ b/src/qtquick/qml/Page.qml @@ -106,6 +106,7 @@ } } NewStuff.QuestionAsker {} + Private.ErrorDisplayer { engine: newStuffEngine; active: root.isCurrentPage; } titleDelegate: QtLayouts.RowLayout { QtLayouts.Layout.fillWidth: true diff --git a/src/qtquick/qml/private/EntryCommentsPage.qml b/src/qtquick/qml/private/EntryCommentsPage.qml --- a/src/qtquick/qml/private/EntryCommentsPage.qml +++ b/src/qtquick/qml/private/EntryCommentsPage.qml @@ -61,6 +61,7 @@ } ] } + ErrorDisplayer { engine: component.itemsModel.engine; active: component.isCurrentPage; } ListView { id: commentsView model: NewStuff.CommentsModel { diff --git a/src/qtquick/qml/private/ErrorDisplayer.qml b/src/qtquick/qml/private/ErrorDisplayer.qml new file mode 100644 --- /dev/null +++ b/src/qtquick/qml/private/ErrorDisplayer.qml @@ -0,0 +1,47 @@ +/* + * Copyright 2020 Dan Leinir Turthra Jensen + * + * This program 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 or + * (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.12 + +MessageBoxSheet { + id: component + title: i18ndc("knewstuff5", "Title for a dialog box which shows error messages", "An Error Occurred"); + property bool active: true; + property QtObject engine; + property QtObject connection: Connections { + target: engine + onErrorMessage: { component.showError(message); } + } + property var errorsToShow: [] + function showError(errorMessage) { + if (active === true) { + errorsToShow.push(errorMessage); + if (sheetOpen === false) { + text = errorsToShow.shift(); + open(); + } + } + } + onSheetOpenChanged: { + if (sheetOpen === false && errorsToShow.length > 0) { + text = errorsToShow.shift(); + open(); + } + } +} diff --git a/src/qtquick/qml/private/MessageBoxSheet.qml b/src/qtquick/qml/private/MessageBoxSheet.qml new file mode 100644 --- /dev/null +++ b/src/qtquick/qml/private/MessageBoxSheet.qml @@ -0,0 +1,60 @@ +/* + * Copyright 2020 Dan Leinir Turthra Jensen + * + * This program 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 or + * (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.12 +import QtQuick.Controls 2.12 as QtControls +import QtQuick.Layouts 1.12 as QtLayouts + +import org.kde.kirigami 2.7 as Kirigami + +Kirigami.OverlaySheet { + id: component + + property alias title: headerLabel.text + property alias text: messageLabel.text + property list actions + + showCloseButton: true + header: Kirigami.Heading { + id: headerLabel + QtLayouts.Layout.fillWidth: true + elide: Text.ElideRight + } + contentItem: TextEdit { + id: messageLabel + QtLayouts.Layout.preferredWidth: Kirigami.Units.gridUnit * 10 + QtLayouts.Layout.margins: Kirigami.Units.largeSpacing + wrapMode: Text.Wrap + readOnly: true + selectByMouse: true + } + footer: QtLayouts.RowLayout { + Item { QtLayouts.Layout.fillWidth: true } + Repeater { + model: component.actions; + QtControls.Button { + action: modelData + Connections { + target: action + onTriggered: component.close() + } + } + } + } +}