diff --git a/discover/CMakeLists.txt b/discover/CMakeLists.txt index 8c094bb1..d6742764 100644 --- a/discover/CMakeLists.txt +++ b/discover/CMakeLists.txt @@ -1,68 +1,69 @@ add_subdirectory(icons) add_subdirectory(autotests) include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/..) add_executable(plasma-discover ${plasma_discover_SRCS} main.cpp DiscoverObject.cpp DiscoverDeclarativePlugin.cpp FeaturedModel.cpp PaginateModel.cpp UnityLauncher.cpp + ReadFile.cpp resources.qrc assets.qrc ) add_executable(Plasma::Discover ALIAS plasma-discover) set_target_properties(plasma-discover PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/plasma-discover) target_link_libraries(plasma-discover PUBLIC KF5::Crash KF5::DBusAddons KF5::I18n KF5::XmlGui KF5::ItemModels KF5::KIOWidgets Qt5::Quick Discover::Common ) install(TARGETS plasma-discover ${INSTALL_TARGETS_DEFAULT_ARGS} ) # if (BUILD_DummyBackend) # target_compile_definitions(plasma-discover PRIVATE $<$:QT_QML_DEBUG=1>) # endif() # Standard desktop file accepts local files as input. set(DesktopNoDisplay "false") set(DesktopMimeType "application/vnd.debian.binary-package;application/x-rpm;") set(DesktopExec "plasma-discover %F") configure_file(org.kde.discover.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/org.kde.discover.desktop) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.discover.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) # Support appstream:// URI set(DesktopNoDisplay "true") set(DesktopMimeType "x-scheme-handler/appstream;") set(DesktopExec "plasma-discover %U") configure_file(org.kde.discover.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/org.kde.discover.urlhandler.desktop) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.discover.urlhandler.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) # support snap:/ URI set(DesktopNoDisplay "true") set(DesktopMimeType "x-scheme-handler/snap;") set(DesktopExec "plasma-discover %U") configure_file(org.kde.discover.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/org.kde.discover.snap.urlhandler.desktop) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.discover.snap.urlhandler.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) if(EXISTS "/etc/debian_version") set(DesktopNoDisplay "true") set(DesktopMimeType "x-scheme-handler/apt") set(DesktopExec "plasma-discover %U") configure_file(org.kde.discover.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/org.kde.discover.apt.urlhandler.desktop) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.discover.apt.urlhandler.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) endif() install(FILES plasmadiscoverui.rc DESTINATION ${KXMLGUI_INSTALL_DIR}/plasmadiscover) install( FILES org.kde.discover.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR} ) diff --git a/discover/DiscoverDeclarativePlugin.cpp b/discover/DiscoverDeclarativePlugin.cpp index 5013a1fd..5cfa4abd 100644 --- a/discover/DiscoverDeclarativePlugin.cpp +++ b/discover/DiscoverDeclarativePlugin.cpp @@ -1,78 +1,80 @@ /* * Copyright (C) 2012 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library/Lesser General Public License * version 2, or (at your option) any later version, as published by the * Free Software Foundation * * 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 General Public License for more details * * You should have received a copy of the GNU Library/Lesser 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. */ #include "DiscoverDeclarativePlugin.h" +#include "ReadFile.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include void DiscoverDeclarativePlugin::initializeEngine(QQmlEngine* engine, const char* uri) { engine->rootContext()->setContextProperty(QStringLiteral("ResourcesModel"), ResourcesModel::global()); engine->rootContext()->setContextProperty(QStringLiteral("TransactionModel"), TransactionModel::global()); engine->rootContext()->setContextProperty(QStringLiteral("SourcesModel"), SourcesModel::global()); engine->rootContext()->setContextProperty(QStringLiteral("CategoryModel"), CategoryModel::global()); QQmlExtensionPlugin::initializeEngine(engine, uri); } void DiscoverDeclarativePlugin::registerTypes(const char* /*uri*/) { qmlRegisterType("org.kde.discover", 2, 0, "TransactionListener"); qmlRegisterType(); qmlRegisterType("org.kde.discover", 2, 0, "ResourcesUpdatesModel"); qmlRegisterType("org.kde.discover", 2, 0, "ResourcesProxyModel"); qmlRegisterType("org.kde.discover", 2, 0, "ReviewsModel"); qmlRegisterType("org.kde.discover", 2, 0, "ApplicationAddonsModel"); qmlRegisterType("org.kde.discover", 2, 0, "ScreenshotsModel"); qmlRegisterType("org.kde.discover", 2, 0, "ActionsModel"); qmlRegisterType("org.kde.discover", 2, 0, "UpdateModel"); + qmlRegisterType("org.kde.discover", 2, 0, "ReadFile"); qmlRegisterUncreatableType("org.kde.discover", 2, 0, "QAction", QStringLiteral("Use QQC Action")); qmlRegisterUncreatableType("org.kde.discover", 2, 0, "AbstractResource", QStringLiteral("should come from the ResourcesModel")); qmlRegisterUncreatableType("org.kde.discover", 2, 0, "AbstractSourcesBackend", QStringLiteral("should come from the SourcesModel")); qmlRegisterUncreatableType("org.kde.discover", 2, 0, "Transaction", QStringLiteral("should come from the backends")); qmlRegisterUncreatableType("org.kde.discover", 2, 0, "SourcesModelClass", QStringLiteral("should come from the backends")); qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); qmlProtectModule("org.kde.discover", 2); qRegisterMetaType>(); } diff --git a/discover/ReadFile.cpp b/discover/ReadFile.cpp new file mode 100644 index 00000000..142db4ce --- /dev/null +++ b/discover/ReadFile.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2018 Aleix Pol Gonzalez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library/Lesser General Public License + * version 2, or (at your option) any later version, as published by the + * Free Software Foundation + * + * 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 General Public License for more details + * + * You should have received a copy of the GNU Library/Lesser 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. + */ + +#include "ReadFile.h" +#include + +ReadFile::ReadFile() +{ + connect(&m_watcher, &QFileSystemWatcher::fileChanged, this, &ReadFile::openNow); + connect(&m_file, &QFile::readyRead, this, &ReadFile::processAll); +} + +void ReadFile::setPath(QString path) +{ + processPath(path); + if (path == m_file.fileName()) + return; + + if (path.isEmpty()) + return;; + + if (m_file.isOpen()) + m_watcher.removePath(m_file.fileName()); + + m_file.setFileName(path); + openNow(); + + m_watcher.addPath(m_file.fileName()); +} + +void ReadFile::openNow() +{ + if (!m_contents.isEmpty()) { + m_contents.clear(); + Q_EMIT contentsChanged(m_contents); + } + m_file.close(); + const auto open = m_file.open(QIODevice::ReadOnly | QIODevice::Text); + Q_EMIT pathChanged(path()); + if (!open) + return; + + m_stream.reset(new QTextStream(&m_file)); + process(800); +} + +void ReadFile::processPath(QString& path) +{ + const QRegularExpression envRx(QStringLiteral("\\$([A-Z_]+)")); + auto matchIt = envRx.globalMatch(path); + while(matchIt.hasNext()) { + auto match = matchIt.next(); + path.replace(match.capturedStart(), match.capturedLength(), QString::fromUtf8(qgetenv(match.capturedRef(1).toUtf8().constData()))); + } +} + +void ReadFile::process(uint max) +{ + QString read = m_stream->readAll(); + if (max>0) + read = read.right(max); + m_contents += read; + Q_EMIT contentsChanged(m_contents); +} diff --git a/discover/ReadFile.h b/discover/ReadFile.h new file mode 100644 index 00000000..4b30a58f --- /dev/null +++ b/discover/ReadFile.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2018 Aleix Pol Gonzalez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library/Lesser General Public License + * version 2, or (at your option) any later version, as published by the + * Free Software Foundation + * + * 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 General Public License for more details + * + * You should have received a copy of the GNU Library/Lesser 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. + */ + +#ifndef READFILE_H +#define READFILE_H + +#include +#include +#include +#include + +class ReadFile : public QObject +{ +Q_OBJECT +Q_PROPERTY(QString contents READ contents NOTIFY contentsChanged) +Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) +public: + ReadFile(); + + QString contents() const { return m_contents; } + QString path() const { return m_file.fileName(); } + void setPath(QString path); + + +Q_SIGNALS: + void pathChanged(const QString &path); + void contentsChanged(const QString &contents); + +private: + void processAll() { return process(0); } + void process(uint max); + void openNow(); + void processPath(QString& path); + + QFile m_file; + QString m_contents; + QSharedPointer m_stream; + QFileSystemWatcher m_watcher; +}; + +#endif // READFILE_H diff --git a/discover/qml/UpdatesPage.qml b/discover/qml/UpdatesPage.qml index 0f692099..6a42c732 100644 --- a/discover/qml/UpdatesPage.qml +++ b/discover/qml/UpdatesPage.qml @@ -1,281 +1,295 @@ import QtQuick.Controls 1.2 import QtQuick.Controls 2.1 as QQC2 import QtQuick.Layouts 1.1 import QtQuick 2.4 import org.kde.discover 2.0 import org.kde.discover.app 1.0 import "navigation.js" as Navigation import org.kde.kirigami 2.1 as Kirigami DiscoverPage { id: page title: i18n("Updates") property string footerLabel: "" ResourcesUpdatesModel { id: resourcesUpdatesModel onPassiveMessage: window.showPassiveNotification(message) onIsProgressingChanged: { if (!isProgressing) { resourcesUpdatesModel.prepare() } } Component.onCompleted: { if (!isProgressing) { resourcesUpdatesModel.prepare() } } } UpdateModel { id: updateModel backend: resourcesUpdatesModel } Kirigami.Action { id: updateAction text: page.unselected>0 ? i18n("Update Selected") : i18n("Update All") visible: updateModel.toUpdateCount iconName: "update-none" enabled: !resourcesUpdatesModel.isProgressing onTriggered: resourcesUpdatesModel.updateAll() } + footer: TextArea { + width: parent.width + height: Kirigami.Units.gridUnit * 10 + text: log.contents + visible: text.length > 0 + + onTextChanged: flickableItem.contentY = flickableItem.contentHeight - flickableItem.height + + ReadFile { + id: log + path: "/var/log/pacman.log" + } + } + Kirigami.Action { id: cancelUpdateAction iconName: "dialog-cancel" text: i18n("Cancel") enabled: resourcesUpdatesModel.transaction && resourcesUpdatesModel.transaction.isCancellable onTriggered: resourcesUpdatesModel.transaction.cancel() } readonly property int unselected: (updateModel.totalUpdatesCount - updateModel.toUpdateCount) readonly property QtObject currentAction: resourcesUpdatesModel.isProgressing ? cancelUpdateAction : updateAction actions { left: refreshAction main: currentAction } header: QQC2.ToolBar { visible: (updateModel.totalUpdatesCount > 0 && resourcesUpdatesModel.isProgressing) || updateModel.hasUpdates RowLayout { anchors.fill: parent LabelBackground { Layout.leftMargin: Kirigami.Units.gridUnit text: updateModel.toUpdateCount + " (" + updateModel.updateSize+")" } QQC2.Label { text: i18n("updates selected") } LabelBackground { id: unselectedItem text: page.unselected visible: page.unselected>0 } QQC2.Label { text: i18n("updates not selected") visible: unselectedItem.visible } Item { Layout.fillWidth: true } enabled: page.currentAction.enabled } } supportsRefreshing: true onRefreshingChanged: { showPassiveNotification("Fetching updates...") ResourcesModel.updateAction.triggered() refreshing = false } ListView { id: updatesView currentIndex: -1 footer: ColumnLayout { anchors.right: parent.right anchors.left: parent.left Kirigami.Heading { Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter horizontalAlignment: Text.AlignHCenter visible: page.footerLabel !== "" text: page.footerLabel } Kirigami.Icon { Layout.alignment: Qt.AlignHCenter visible: page.footerLabel !== "" source: "update-none" opacity: 0.3 width: 200 height: 200 } Item { visible: page.footerLabel === "" height: Kirigami.Units.gridUnit width: 1 } } model: updateModel section { property: "section" delegate: Kirigami.Heading { x: Kirigami.Units.gridUnit level: 2 text: section } } spacing: Kirigami.Units.smallSpacing delegate: Kirigami.AbstractListItem { backgroundColor: Kirigami.Theme.viewBackgroundColor x: Kirigami.Units.gridUnit width: ListView.view.width - Kirigami.Units.gridUnit * 2 highlighted: ListView.isCurrentItem onEnabledChanged: if (!enabled) { layout.extended = false; } Keys.onReturnPressed: { itemChecked.clicked() } Keys.onPressed: if (event.key===Qt.Key_Alt) layout.extended = true Keys.onReleased: if (event.key===Qt.Key_Alt) layout.extended = false ColumnLayout { id: layout property bool extended: false onExtendedChanged: if (extended) { updateModel.fetchChangelog(index) } RowLayout { Layout.fillWidth: true Layout.fillHeight: true CheckBox { id: itemChecked Layout.alignment: Qt.AlignVCenter checked: model.checked == Qt.Checked onClicked: model.checked = (model.checked==Qt.Checked ? Qt.Unchecked : Qt.Checked) } Kirigami.Icon { Layout.fillHeight: true Layout.preferredWidth: height source: decoration smooth: true } QQC2.Label { Layout.fillWidth: true text: i18n("%1 (%2)", display, version) elide: Text.ElideRight } LabelBackground { Layout.minimumWidth: Kirigami.Units.gridUnit * 6 text: size progress: resourceProgress/100 } } QQC2.Frame { Layout.fillWidth: true implicitHeight: view.contentHeight visible: layout.extended && changelog.length>0 QQC2.Label { id: view anchors { right: parent.right left: parent.left } text: changelog textFormat: Text.StyledText wrapMode: Text.WordWrap onLinkActivated: Qt.openUrlExternally(link) } //This saves a binding loop on implictHeight, as the Label //height is updated twice (first time with the wrong value) Behavior on implicitHeight { PropertyAnimation { duration: Kirigami.Units.shortDuration } } } Button { Layout.alignment: Qt.AlignRight text: i18n("More Information...") visible: layout.extended enabled: !resourcesUpdatesModel.isProgressing onClicked: Navigation.openApplication(resource) } } onClicked: { layout.extended = !layout.extended } } } readonly property alias secSinceUpdate: resourcesUpdatesModel.secsToLastUpdate state: ( updateModel.hasUpdates ? "has-updates" : ResourcesModel.isFetching ? "fetching" : resourcesUpdatesModel.isProgressing ? "progressing" : secSinceUpdate < 0 ? "unknown" : secSinceUpdate === 0 ? "now-uptodate" : secSinceUpdate < 1000 * 60 * 60 * 24 ? "uptodate" : secSinceUpdate < 1000 * 60 * 60 * 24 * 7 ? "medium" : "low" ) states: [ State { name: "fetching" PropertyChanges { target: page; title: i18nc("@info", "Fetching...") } PropertyChanges { target: page; footerLabel: i18nc("@info", "Looking for updates") } }, State { name: "progressing" PropertyChanges { target: page; title: i18nc("@info", "Updating...") } PropertyChanges { target: page; footerLabel: resourcesUpdatesModel.progress<=0 ? i18nc("@info", "Fetching updates") : "" } }, State { name: "has-updates" PropertyChanges { target: page; title: i18nc("@info", "Updates") } }, State { name: "now-uptodate" PropertyChanges { target: page; title: i18nc("@info", "The system is up to date") } PropertyChanges { target: page; footerLabel: i18nc("@info", "No updates") } }, State { name: "uptodate" PropertyChanges { target: page; title: i18nc("@info", "The system is up to date") } PropertyChanges { target: page; footerLabel: i18nc("@info", "No updates") } }, State { name: "medium" PropertyChanges { target: page; title: i18nc("@info", "No updates are available") } }, State { name: "low" PropertyChanges { target: page; title: i18nc("@info", "Should check for updates") } }, State { name: "unknown" PropertyChanges { target: page; title: i18nc("@info", "It is unknown when the last check for updates was") } } ] }