diff --git a/Apper/CMakeLists.txt b/Apper/CMakeLists.txt index 132549b..2b71603 100644 --- a/Apper/CMakeLists.txt +++ b/Apper/CMakeLists.txt @@ -1,60 +1,60 @@ # CMakeLists for the apper executable and misc data set(apper_SRCS Settings/Settings.cpp Settings/OriginModel.cpp Updater/UpdateDetails.cpp Updater/DistroUpgrade.cpp Updater/CheckableHeader.cpp Updater/Updater.cpp FiltersMenu.cpp ClickableLabel.cpp ScreenShotViewer.cpp PackageDetails.cpp GraphicsOpacityDropShadowEffect.cpp CategoryModel.cpp BrowseView.cpp TransactionModel.cpp TransactionFilterModel.cpp TransactionHistory.cpp ApperKCM.cpp ApperKCM.h MainUi.cpp BackendDetails.cpp Apper.cpp main.cpp ) ki18n_wrap_ui(apper_SRCS BackendDetails.ui Settings/Settings.ui Updater/UpdateDetails.ui Updater/Updater.ui PackageDetails.ui BrowseView.ui TransactionHistory.ui ApperKCM.ui ) add_executable(apper ${apper_SRCS} ) target_link_libraries(apper KF5::IconThemes KF5::DBusAddons - ${PackageKitQt5_LIBRARIES} + PK::packagekitqt5 apper_private ) set_target_properties(apper PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/apper) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/apper DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES org.kde.apper.desktop DESTINATION ${CMAKE_INSTALL_APPDIR}) install(FILES org.kde.apper_installer.desktop DESTINATION ${CMAKE_INSTALL_APPDIR}) install(FILES org.kde.apper_settings.desktop DESTINATION ${CMAKE_INSTALL_APPDIR}) install(FILES org.kde.apper_updates.desktop DESTINATION ${CMAKE_INSTALL_APPDIR}) install(FILES org.kde.apper.appdata.xml DESTINATION ${CMAKE_INSTALL_METAINFODIR}) add_subdirectory(Icons) add_subdirectory(Animations) diff --git a/Apper/PackageDetails.cpp b/Apper/PackageDetails.cpp index 3a36763..28de201 100644 --- a/Apper/PackageDetails.cpp +++ b/Apper/PackageDetails.cpp @@ -1,802 +1,800 @@ /*************************************************************************** - * Copyright (C) 2009-2011 by Daniel Nicoletti * + * Copyright (C) 2009-2018 by Daniel Nicoletti * * dantti12@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; see the file COPYING. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "PackageDetails.h" #include "ui_PackageDetails.h" #include "ScreenShotViewer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_APPSTREAM #include #endif #include "GraphicsOpacityDropShadowEffect.h" #define BLUR_RADIUS 15 #define FINAL_HEIGHT 210 using namespace PackageKit; Q_DECLARE_LOGGING_CATEGORY(APPER) Q_DECLARE_METATYPE(KPixmapSequenceOverlayPainter**) PackageDetails::PackageDetails(QWidget *parent) : QWidget(parent), ui(new Ui::PackageDetails), m_busySeq(0), m_display(false), m_hideVersion(false), m_hideArch(false), m_transaction(0), m_hasDetails(false), m_hasFileList(false) { ui->setupUi(this); ui->hideTB->setIcon(QIcon::fromTheme(QLatin1String("window-close"))); connect(ui->hideTB, SIGNAL(clicked()), this, SLOT(hide())); auto menu = new QMenu(i18n("Display"), this); m_actionGroup = new QActionGroup(this); // we check to see which roles are supported by the backend // if so we ask for information and create the containers descriptionAction = menu->addAction(i18n("Description")); descriptionAction->setCheckable(true); descriptionAction->setData(PackageKit::Transaction::RoleGetDetails); m_actionGroup->addAction(descriptionAction); ui->descriptionW->setWidgetResizable(true); dependsOnAction = menu->addAction(i18n("Depends On")); dependsOnAction->setCheckable(true); dependsOnAction->setData(PackageKit::Transaction::RoleDependsOn); m_actionGroup->addAction(dependsOnAction); // Sets a transparent background QWidget *dependsViewport = ui->dependsOnLV->viewport(); QPalette dependsPalette = dependsViewport->palette(); dependsPalette.setColor(dependsViewport->backgroundRole(), Qt::transparent); dependsPalette.setColor(dependsViewport->foregroundRole(), dependsPalette.color(QPalette::WindowText)); dependsViewport->setPalette(dependsPalette); m_dependsModel = new PackageModel(this); m_dependsProxy = new QSortFilterProxyModel(this); m_dependsProxy->setDynamicSortFilter(true); m_dependsProxy->setSortRole(PackageModel::SortRole); m_dependsProxy->setSourceModel(m_dependsModel); ui->dependsOnLV->setModel(m_dependsProxy); ui->dependsOnLV->sortByColumn(0, Qt::AscendingOrder); ui->dependsOnLV->header()->setDefaultAlignment(Qt::AlignCenter); ui->dependsOnLV->header()->setSectionResizeMode(PackageModel::NameCol, QHeaderView::ResizeToContents); ui->dependsOnLV->header()->setSectionResizeMode(PackageModel::VersionCol, QHeaderView::ResizeToContents); ui->dependsOnLV->header()->setSectionResizeMode(PackageModel::ArchCol, QHeaderView::Stretch); ui->dependsOnLV->header()->hideSection(PackageModel::ActionCol); ui->dependsOnLV->header()->hideSection(PackageModel::CurrentVersionCol); ui->dependsOnLV->header()->hideSection(PackageModel::OriginCol); ui->dependsOnLV->header()->hideSection(PackageModel::SizeCol); requiredByAction = menu->addAction(i18n("Required By")); requiredByAction->setCheckable(true); requiredByAction->setData(PackageKit::Transaction::RoleRequiredBy); m_actionGroup->addAction(requiredByAction); // Sets a transparent background QWidget *requiredViewport = ui->requiredByLV->viewport(); QPalette requiredPalette = requiredViewport->palette(); requiredPalette.setColor(requiredViewport->backgroundRole(), Qt::transparent); requiredPalette.setColor(requiredViewport->foregroundRole(), requiredPalette.color(QPalette::WindowText)); requiredViewport->setPalette(requiredPalette); m_requiresModel = new PackageModel(this); m_requiresProxy = new QSortFilterProxyModel(this); m_requiresProxy->setDynamicSortFilter(true); m_requiresProxy->setSortRole(PackageModel::SortRole); m_requiresProxy->setSourceModel(m_requiresModel); ui->requiredByLV->setModel(m_requiresProxy); ui->requiredByLV->sortByColumn(0, Qt::AscendingOrder); ui->requiredByLV->header()->setDefaultAlignment(Qt::AlignCenter); ui->requiredByLV->header()->setSectionResizeMode(PackageModel::NameCol, QHeaderView::ResizeToContents); ui->requiredByLV->header()->setSectionResizeMode(PackageModel::VersionCol, QHeaderView::ResizeToContents); ui->requiredByLV->header()->setSectionResizeMode(PackageModel::ArchCol, QHeaderView::Stretch); ui->requiredByLV->header()->hideSection(PackageModel::ActionCol); ui->requiredByLV->header()->hideSection(PackageModel::CurrentVersionCol); ui->requiredByLV->header()->hideSection(PackageModel::OriginCol); ui->requiredByLV->header()->hideSection(PackageModel::SizeCol); fileListAction = menu->addAction(i18n("File List")); fileListAction->setCheckable(true); fileListAction->setData(PackageKit::Transaction::RoleGetFiles); m_actionGroup->addAction(fileListAction); // Sets a transparent background QWidget *actionsViewport = ui->filesPTE->viewport(); QPalette palette = actionsViewport->palette(); palette.setColor(actionsViewport->backgroundRole(), Qt::transparent); palette.setColor(actionsViewport->foregroundRole(), palette.color(QPalette::WindowText)); actionsViewport->setPalette(palette); // Set the menu ui->menuTB->setMenu(menu); ui->menuTB->setIcon(QIcon::fromTheme(QLatin1String("help-about"))); connect(m_actionGroup, SIGNAL(triggered(QAction*)), this, SLOT(actionActivated(QAction*))); m_busySeq = new KPixmapSequenceOverlayPainter(this); m_busySeq->setSequence(KPixmapSequence(QLatin1String("process-working"), KIconLoader::SizeSmallMedium)); m_busySeq->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); m_busySeq->setWidget(ui->stackedWidget); // Setup the opacit effect that makes the descriptio transparent // after finished it checks in display() to see if it shouldn't show // up again. The property animation is always the same, the only different thing // is the Forward or Backward property QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect(ui->stackedWidget); effect->setOpacity(0); ui->stackedWidget->setGraphicsEffect(effect); m_fadeStacked = new QPropertyAnimation(effect, "opacity", this); m_fadeStacked->setDuration(500); m_fadeStacked->setStartValue(qreal(0)); m_fadeStacked->setEndValue(qreal(1)); connect(m_fadeStacked, SIGNAL(finished()), this, SLOT(display())); // It's is impossible due to some limitation in Qt to set two effects on the same // Widget m_fadeScreenshot = new QPropertyAnimation(effect, "opacity", this); GraphicsOpacityDropShadowEffect *shadow = new GraphicsOpacityDropShadowEffect(ui->screenshotL); shadow->setOpacity(0); shadow->setBlurRadius(BLUR_RADIUS); shadow->setOffset(2); shadow->setColor(QApplication::palette().dark().color()); ui->screenshotL->setGraphicsEffect(shadow); m_fadeScreenshot = new QPropertyAnimation(shadow, "opacity", this); m_fadeScreenshot->setDuration(500); m_fadeScreenshot->setStartValue(qreal(0)); m_fadeScreenshot->setEndValue(qreal(1)); connect(m_fadeScreenshot, SIGNAL(finished()), this, SLOT(display())); // This pannel expanding QPropertyAnimation *anim1 = new QPropertyAnimation(this, "maximumSize", this); anim1->setDuration(500); anim1->setEasingCurve(QEasingCurve::OutQuart); anim1->setStartValue(QSize(QWIDGETSIZE_MAX, 0)); anim1->setEndValue(QSize(QWIDGETSIZE_MAX, FINAL_HEIGHT)); QPropertyAnimation *anim2 = new QPropertyAnimation(this, "minimumSize", this); anim2->setDuration(500); anim2->setEasingCurve(QEasingCurve::OutQuart); anim2->setStartValue(QSize(QWIDGETSIZE_MAX, 0)); anim2->setEndValue(QSize(QWIDGETSIZE_MAX, FINAL_HEIGHT)); m_expandPanel = new QParallelAnimationGroup(this); m_expandPanel->addAnimation(anim1); m_expandPanel->addAnimation(anim2); connect(m_expandPanel, SIGNAL(finished()), this, SLOT(display())); } void PackageDetails::init(PackageKit::Transaction::Roles roles) { // qCDebug(); bool setChecked = true; if (roles & PackageKit::Transaction::RoleGetDetails) { descriptionAction->setEnabled(true); descriptionAction->setChecked(setChecked); setChecked = false; } else { descriptionAction->setEnabled(false); descriptionAction->setChecked(false); } if (roles & PackageKit::Transaction::RoleDependsOn) { dependsOnAction->setEnabled(true); dependsOnAction->setChecked(setChecked); setChecked = false; } else { dependsOnAction->setEnabled(false); dependsOnAction->setChecked(false); } if (roles & PackageKit::Transaction::RoleRequiredBy) { requiredByAction->setEnabled(true); requiredByAction->setChecked(setChecked); setChecked = false; } else { requiredByAction->setEnabled(false); requiredByAction->setChecked(false); } if (roles & PackageKit::Transaction::RoleGetFiles) { fileListAction->setEnabled(true); fileListAction->setChecked(setChecked); setChecked = false; } else { fileListAction->setEnabled(false); fileListAction->setChecked(false); } } PackageDetails::~PackageDetails() { delete ui; } void PackageDetails::setPackage(const QModelIndex &index) { qCDebug(APPER) << index; QString appId = index.data(PackageModel::ApplicationId).toString(); QString packageID = index.data(PackageModel::IdRole).toString(); // if it's the same package and the same application, return if (packageID == m_packageID && appId == m_appId) { return; } else if (maximumSize().height() == 0) { // Expand the panel m_display = true; m_expandPanel->setDirection(QAbstractAnimation::Forward); m_expandPanel->start(); } else { // Hide the old description fadeOut(PackageDetails::FadeScreenshot | PackageDetails::FadeStacked); } m_index = index; m_appId = appId; m_packageID = packageID; m_hasDetails = false; m_hasFileList = false; m_hasRequires = false; m_hasDepends = false; qCDebug(APPER) << "appId" << appId << "m_package" << m_packageID; QString pkgIconPath = index.data(PackageModel::IconRole).toString(); m_currentIcon = PkIcons::getIcon(pkgIconPath, QString()).pixmap(64, 64); m_appName = index.data(PackageModel::NameRole).toString(); m_currentScreenshot = thumbnail(Transaction::packageName(m_packageID)); qCDebug(APPER) << "current screenshot" << m_currentScreenshot; if (!m_currentScreenshot.isNull()) { if (m_screenshotPath.contains(m_currentScreenshot)) { display(); } else { auto tempFile = new QTemporaryFile; tempFile->open(); KIO::FileCopyJob *job = KIO::file_copy(QUrl(m_currentScreenshot), QUrl(tempFile->fileName()), -1, KIO::Overwrite | KIO::HideProgressInfo); connect(job, &KIO::FileCopyJob::result, this, &PackageDetails::resultJob); } } if (m_actionGroup->checkedAction()) { actionActivated(m_actionGroup->checkedAction()); } } void PackageDetails::on_screenshotL_clicked() { - QString url; - url = screenshot(Transaction::packageName(m_packageID)); - if (!url.isNull()) { - ScreenShotViewer *view = new ScreenShotViewer(url); + const QUrl url = screenshot(Transaction::packageName(m_packageID)); + if (!url.isEmpty()) { + auto view = new ScreenShotViewer(url); view->setWindowTitle(m_appName); view->show(); } } void PackageDetails::hidePackageVersion(bool hide) { m_hideVersion = hide; } void PackageDetails::hidePackageArch(bool hide) { m_hideArch = hide; } void PackageDetails::actionActivated(QAction *action) { // don't fade the screenshot // if the package changed setPackage() fades both fadeOut(FadeStacked); qCDebug(APPER); // disconnect the transaction // so that we don't get old data if (m_transaction) { disconnect(m_transaction, SIGNAL(details(PackageKit::Details)), this, SLOT(description(PackageKit::Details))); disconnect(m_transaction, SIGNAL(package(PackageKit::Transaction::Info,QString,QString)), m_dependsModel, SLOT(addPackage(PackageKit::Transaction::Info,QString,QString))); disconnect(m_transaction, SIGNAL(package(PackageKit::Transaction::Info,QString,QString)), m_requiresModel, SLOT(addPackage(PackageKit::Transaction::Info,QString,QString))); disconnect(m_transaction, SIGNAL(files(QString,QStringList)), this, SLOT(files(QString,QStringList))); disconnect(m_transaction, SIGNAL(finished(PackageKit::Transaction::Exit,uint)), this, SLOT(finished())); m_transaction = 0; } // Check to see if we don't already have the required data uint role = action->data().toUInt(); switch (role) { case PackageKit::Transaction::RoleGetDetails: if (m_hasDetails) { description(m_details); display(); return; } break; case PackageKit::Transaction::RoleDependsOn: if (m_hasDepends) { display(); return; } break; case PackageKit::Transaction::RoleRequiredBy: if (m_hasRequires) { display(); return; } break; case PackageKit::Transaction::RoleGetFiles: if (m_hasFileList) { display(); return; } break; } // we don't have the data qCDebug(APPER) << "New transaction"; switch (role) { case PackageKit::Transaction::RoleGetDetails: m_transaction = Daemon::getDetails(m_packageID); connect(m_transaction, SIGNAL(details(PackageKit::Details)), SLOT(description(PackageKit::Details))); break; case PackageKit::Transaction::RoleDependsOn: m_dependsModel->clear(); m_transaction = Daemon::dependsOn(m_packageID, PackageKit::Transaction::FilterNone, false); connect(m_transaction, SIGNAL(package(PackageKit::Transaction::Info,QString,QString)), m_dependsModel, SLOT(addPackage(PackageKit::Transaction::Info,QString,QString))); connect(m_transaction, SIGNAL(finished(PackageKit::Transaction::Exit,uint)), m_dependsModel, SLOT(finished())); break; case PackageKit::Transaction::RoleRequiredBy: m_requiresModel->clear(); m_transaction = Daemon::requiredBy(m_packageID, PackageKit::Transaction::FilterNone, false); connect(m_transaction, SIGNAL(package(PackageKit::Transaction::Info,QString,QString)), m_requiresModel, SLOT(addPackage(PackageKit::Transaction::Info,QString,QString))); connect(m_transaction, SIGNAL(finished(PackageKit::Transaction::Exit,uint)), m_requiresModel, SLOT(finished())); break; case PackageKit::Transaction::RoleGetFiles: m_currentFileList.clear(); m_transaction = Daemon::getFiles(m_packageID); connect(m_transaction, SIGNAL(files(QString,QStringList)), this, SLOT(files(QString,QStringList))); break; default: qWarning() << Q_FUNC_INFO << "Oops, unhandled role, please report" << role; return; } connect(m_transaction, SIGNAL(finished(PackageKit::Transaction::Exit,uint)), this, SLOT(finished())); qCDebug(APPER) <<"transaction running"; m_busySeq->start(); } void PackageDetails::resultJob(KJob *job) { KIO::FileCopyJob *fJob = qobject_cast(job); if (!fJob->error()) { m_screenshotPath[fJob->srcUrl().url()] = fJob->destUrl().toLocalFile(); display(); } } void PackageDetails::hide() { m_display = false; // Clean the old description otherwise if the user selects the same // package the pannel won't expand m_packageID.clear(); m_appId.clear(); if (maximumSize().height() == FINAL_HEIGHT) { if (m_fadeStacked->currentValue().toReal() == 0 && m_fadeScreenshot->currentValue().toReal() == 0) { // Screen shot and description faded let's shrink the pannel m_expandPanel->setDirection(QAbstractAnimation::Backward); m_expandPanel->start(); } else { // Hide current description fadeOut(PackageDetails::FadeScreenshot | PackageDetails::FadeStacked); } } } void PackageDetails::fadeOut(FadeWidgets widgets) { // Fade out only if needed if ((widgets & FadeStacked) && m_fadeStacked->currentValue().toReal() != 0) { m_fadeStacked->setDirection(QAbstractAnimation::Backward); m_fadeStacked->start(); } // Fade out the screenshot only if needed if ((widgets & FadeScreenshot) && m_fadeScreenshot->currentValue().toReal() != 0) { ui->screenshotL->unsetCursor(); m_fadeScreenshot->setDirection(QAbstractAnimation::Backward); m_fadeScreenshot->start(); } } void PackageDetails::display() { // If we shouldn't be showing hide the pannel if (!m_display) { hide(); } else if (maximumSize().height() == FINAL_HEIGHT) { emit ensureVisible(m_index); // Check to see if the stacked widget is transparent if (m_fadeStacked->currentValue().toReal() == 0 && m_actionGroup->checkedAction()) { bool fadeIn = false; switch (m_actionGroup->checkedAction()->data().toUInt()) { case PackageKit::Transaction::RoleGetDetails: if (m_hasDetails) { setupDescription(); fadeIn = true; } break; case PackageKit::Transaction::RoleDependsOn: if (m_hasDepends) { if (ui->stackedWidget->currentWidget() != ui->pageDepends) { ui->stackedWidget->setCurrentWidget(ui->pageDepends); } fadeIn = true; } break; case PackageKit::Transaction::RoleRequiredBy: if (m_hasRequires) { if (ui->stackedWidget->currentWidget() != ui->pageRequired) { ui->stackedWidget->setCurrentWidget(ui->pageRequired); } fadeIn = true; } break; case PackageKit::Transaction::RoleGetFiles: if (m_hasFileList) { ui->filesPTE->clear(); if (m_currentFileList.isEmpty()) { ui->filesPTE->insertPlainText(i18n("No files were found.")); } else { m_currentFileList.sort(); ui->filesPTE->insertPlainText(m_currentFileList.join(QLatin1Char('\n'))); } if (ui->stackedWidget->currentWidget() != ui->pageFiles) { ui->stackedWidget->setCurrentWidget(ui->pageFiles); } ui->filesPTE->verticalScrollBar()->setValue(0); fadeIn = true; } break; } if (fadeIn) { // Fade In m_fadeStacked->setDirection(QAbstractAnimation::Forward); m_fadeStacked->start(); } } // Check to see if we have a screen shot and if we are // transparent, and make sure the details are going // to be shown if (m_fadeScreenshot->currentValue().toReal() == 0 && m_screenshotPath.contains(m_currentScreenshot) && m_fadeStacked->direction() == QAbstractAnimation::Forward) { QPixmap pixmap; pixmap = QPixmap(m_screenshotPath[m_currentScreenshot]) .scaled(160,120, Qt::KeepAspectRatio, Qt::SmoothTransformation); ui->screenshotL->setPixmap(pixmap); ui->screenshotL->setCursor(Qt::PointingHandCursor); // Fade In m_fadeScreenshot->setDirection(QAbstractAnimation::Forward); m_fadeScreenshot->start(); } } } void PackageDetails::setupDescription() { if (ui->stackedWidget->currentWidget() != ui->pageDescription) { ui->stackedWidget->setCurrentWidget(ui->pageDescription); } if (!m_hasDetails) { // Oops we don't have any details ui->descriptionL->setText(i18n("Could not fetch software details")); ui->descriptionL->show(); // Hide stuff so we don't display outdated data ui->homepageL->hide(); ui->pathL->hide(); ui->licenseL->hide(); ui->sizeL->hide(); ui->iconL->clear(); } if (!m_detailsDescription.isEmpty()) { ui->descriptionL->setText(m_detailsDescription.replace(QLatin1Char('\n'), QLatin1String("
"))); ui->descriptionL->show(); } else { ui->descriptionL->clear(); } if (!m_details.url().isEmpty()) { ui->homepageL->setText(QLatin1String("") + m_details.url() + QLatin1String("")); ui->homepageL->show(); } else { ui->homepageL->hide(); } // Let's try to find the application's path in human user // readable easiest form :D KService::Ptr service = KService::serviceByDesktopName(m_appId); QVector > ret; if (service) { ret = locateApplication(QString(), service->menuId()); } if (ret.isEmpty()) { ui->pathL->hide(); } else { QString path; path.append(QString(QLatin1String("")) .arg(KIconLoader::global()->iconPath(QLatin1String("kde"), KIconLoader::Small))); path.append(QString(QLatin1String(" %1  %3")) .arg(QString::fromUtf8("➜")) .arg(KIconLoader::global()->iconPath(QLatin1String("applications-other"), KIconLoader::Small)) .arg(i18n("Applications"))); for (int i = 0; i < ret.size(); i++) { path.append(QString(QLatin1String(" %1  %3")) .arg(QString::fromUtf8("➜")) .arg(KIconLoader::global()->iconPath(ret.at(i).second, KIconLoader::Small)) .arg(ret.at(i).first)); } ui->pathL->setText(path); ui->pathL->show(); } // if (details->group() != Package::UnknownGroup) { // // description += "" + i18nc("Group of the package", "Group") + ":" // // + PkStrings::groups(details->group()) // // + ""; // } if (!m_details.license().isEmpty() && m_details.license() != QLatin1String("unknown")) { // We have a license, check if we have and should show show package version if (!m_hideVersion && !Transaction::packageVersion(m_details.packageId()).isEmpty()) { ui->licenseL->setText(Transaction::packageVersion(m_details.packageId()) + QLatin1String(" - ") + m_details.license()); } else { ui->licenseL->setText(m_details.license()); } ui->licenseL->show(); } else if (!m_hideVersion) { ui->licenseL->setText(Transaction::packageVersion(m_details.packageId())); ui->licenseL->show(); } else { ui->licenseL->hide(); } if (m_details.size() > 0) { QString size = KFormat().formatByteSize(m_details.size()); if (!m_hideArch && !Transaction::packageArch(m_details.packageId()).isEmpty()) { ui->sizeL->setText(size % QLatin1String(" (") % Transaction::packageArch(m_details.packageId()) % QLatin1Char(')')); } else { ui->sizeL->setText(size); } ui->sizeL->show(); } else if (!m_hideArch && !Transaction::packageArch(m_details.packageId()).isEmpty()) { ui->sizeL->setText(Transaction::packageArch(m_details.packageId())); } else { ui->sizeL->hide(); } if (m_currentIcon.isNull()) { ui->iconL->clear(); } else { ui->iconL->setPixmap(m_currentIcon); } } QVector > PackageDetails::locateApplication(const QString &_relPath, const QString &menuId) const { QVector > ret; KServiceGroup::Ptr root = KServiceGroup::group(_relPath); if (!root || !root->isValid()) { return ret; } KServiceGroup::List list = root->entries(false /* sorted */, true /* exclude no display entries */, false /* allow separators */); //! TODO: Port to KF5 properly Q_UNUSED(menuId) #if 0 for (KServiceGroup::List::ConstIterator it = list.begin(); it != list.end(); it++) { KSycocaEntry::Ptr = (*it); if (p->isType(KST_KService)) { KService *service = static_cast(p.get()); if (service->noDisplay()) { continue; } // qCDebug(APPER) << menuId << service->menuId(); if (service->menuId() == menuId) { QPair pair; pair.first = service->name(); pair.second = service->icon(); ret << pair; // qCDebug(APPER) << "FOUND!"; return ret; } } else if (p->isType(KST_KServiceGroup)) { KServiceGroup *serviceGroup = static_cast(p.get()); if (serviceGroup->noDisplay() || serviceGroup->childCount() == 0) { continue; } QVector > found; found = locateApplication(serviceGroup->relPath(), menuId); if (!found.isEmpty()) { QPair pair; pair.first = serviceGroup->caption(); pair.second = serviceGroup->icon(); ret << pair; ret << found; return ret; } } else { kWarning(250) << "KServiceGroup: Unexpected object in list!"; continue; } } #endif return ret; } QString PackageDetails::thumbnail(const QString &pkgName) const { #ifndef HAVE_APPSTREAM Q_UNUSED(pkgName) return QString(); #else return QString();//AppStream::instance()->thumbnail(pkgName); #endif } -QString PackageDetails::screenshot(const QString &pkgName) const +QUrl PackageDetails::screenshot(const QString &pkgName) const { #ifndef HAVE_APPSTREAM Q_UNUSED(pkgName) - return QString(); + return QUrl(); #else - return QString();// AppStream::instance()->screenshot(pkgName); + return AppStreamHelper::instance()->screenshot(pkgName); #endif } void PackageDetails::description(const PackageKit::Details &details) { qCDebug(APPER) << details; m_details = details; m_detailsDescription = details.description(); m_hasDetails = true; #ifdef HAVE_APPSTREAM // check if we have application details from Appstream data // FIXME: The whole AppStream handling sucks badly, since it was added later // and on to of the package-based model. So we can't respect the "multiple apps // in one package" case here. - QList apps; -// apps = AppStream::instance()->applications(Transaction::packageName(m_packageID)); - for (const AppStream::Application &app : apps) { - if (!app.description.isEmpty()) { - m_detailsDescription = app.description; + const QList apps = AppStreamHelper::instance()->applications(Transaction::packageName(m_packageID)); + for (const AppStream::Component &app : apps) { + if (!app.description().isEmpty()) { + m_detailsDescription = app.description(); break; } } #endif } void PackageDetails::finished() { if (m_busySeq) { m_busySeq->stop(); } m_transaction = 0; auto transaction = qobject_cast(sender()); qCDebug(APPER); if (transaction) { qCDebug(APPER) << transaction->role() << PackageKit::Transaction::RoleGetDetails; if (transaction->role() == PackageKit::Transaction::RoleGetDetails) { m_hasDetails = true; } else if (transaction->role() == PackageKit::Transaction::RoleGetFiles) { m_hasFileList = true; } else if (transaction->role() == PackageKit::Transaction::RoleRequiredBy) { m_hasRequires = true; } else if (transaction->role() == PackageKit::Transaction::RoleDependsOn) { m_hasDepends = true; } else { return; } display(); } } void PackageDetails::files(const QString &packageID, const QStringList &files) { Q_UNUSED(packageID) m_currentFileList = files; } diff --git a/Apper/PackageDetails.h b/Apper/PackageDetails.h index c85a094..a900499 100644 --- a/Apper/PackageDetails.h +++ b/Apper/PackageDetails.h @@ -1,137 +1,137 @@ /*************************************************************************** * Copyright (C) 2009-2010 by Daniel Nicoletti * * dantti12@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; see the file COPYING. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef PACKAGE_DETAILS_H #define PACKAGE_DETAILS_H #include #include
#include #include #include #include #include #include #include namespace Ui { class PackageDetails; } class PackageModel; class PackageDetails : public QWidget { Q_OBJECT public: enum FadeWidget { FadeNone = 0x0, FadeStacked = 0x1, FadeScreenshot = 0x2 }; Q_DECLARE_FLAGS(FadeWidgets, FadeWidget) explicit PackageDetails(QWidget *parent = 0); ~PackageDetails(); void init(PackageKit::Transaction::Roles roles); void setPackage(const QModelIndex &index); void hidePackageVersion(bool hide); void hidePackageArch(bool hide); public Q_SLOTS: void hide(); Q_SIGNALS: void ensureVisible(const QModelIndex &index); private Q_SLOTS: void on_screenshotL_clicked(); void actionActivated(QAction *action); void description(const PackageKit::Details &details); void files(const QString &packageID, const QStringList &files); void finished(); void resultJob(KJob *); void display(); private: void fadeOut(FadeWidgets widgets); void setupDescription(); QVector > locateApplication(const QString &_relPath, const QString &menuId) const; QString thumbnail(const QString &pkgName) const; - QString screenshot(const QString &pkgName) const; + QUrl screenshot(const QString &pkgName) const; Ui::PackageDetails *ui; QActionGroup *m_actionGroup; QModelIndex m_index; QString m_packageID; PackageKit::Details m_details; QString m_detailsDescription; QAction *descriptionAction; QAction *dependsOnAction; QAction *requiredByAction; QAction *fileListAction; QString m_appName; QParallelAnimationGroup *m_expandPanel; KPixmapSequenceOverlayPainter *m_busySeq; QPropertyAnimation *m_fadeStacked; QPropertyAnimation *m_fadeScreenshot; bool m_display; bool m_hideVersion; bool m_hideArch; // We need a copy of prety much every thing // we have, so that we update only when we are // totaly transparent this way the user // does not see the ui flicker PackageKit::Transaction *m_transaction; bool m_hasDetails; QString m_currentText; QPixmap m_currentIcon; QString m_appId; // file list buffer bool m_hasFileList; QStringList m_currentFileList; // GetDepends buffer bool m_hasDepends; PackageModel *m_dependsModel; QSortFilterProxyModel *m_dependsProxy; // GetRequires buffer bool m_hasRequires; PackageModel *m_requiresModel; QSortFilterProxyModel *m_requiresProxy; // Screen shot buffer QString m_currentScreenshot; QHash m_screenshotPath; }; Q_DECLARE_OPERATORS_FOR_FLAGS(PackageDetails::FadeWidgets) #endif diff --git a/Apper/ScreenShotViewer.cpp b/Apper/ScreenShotViewer.cpp index b274017..ef2206b 100644 --- a/Apper/ScreenShotViewer.cpp +++ b/Apper/ScreenShotViewer.cpp @@ -1,107 +1,107 @@ /*************************************************************************** * Copyright (C) 2009-2018 by Daniel Nicoletti * * dantti12@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; see the file COPYING. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "ScreenShotViewer.h" #include #include #include #include #include #include #include #include #include #include "ClickableLabel.h" -ScreenShotViewer::ScreenShotViewer(const QString &url, QWidget *parent) +ScreenShotViewer::ScreenShotViewer(const QUrl &url, QWidget *parent) : QScrollArea(parent) { m_screenshotL = new ClickableLabel(this); m_screenshotL->setCursor(Qt::PointingHandCursor); m_screenshotL->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); m_screenshotL->resize(250, 200); resize(250, 200); setFrameShape(NoFrame); setFrameShadow(Plain); setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); setWidget(m_screenshotL); setWindowIcon(QIcon::fromTheme(QLatin1String("layer-visible-on"))); auto tempFile = new QTemporaryFile; // tempFile->setPrefix("appgetfull"); // tempFile->setSuffix(".png"); tempFile->open(); - KIO::FileCopyJob *job = KIO::file_copy(QUrl(url), + KIO::FileCopyJob *job = KIO::file_copy(url, QUrl(tempFile->fileName()), -1, KIO::Overwrite | KIO::HideProgressInfo); connect(job, &KIO::FileCopyJob::result, this, &ScreenShotViewer::resultJob); m_busySeq = new KPixmapSequenceOverlayPainter(this); m_busySeq->setSequence(KPixmapSequence(QLatin1String("process-working"), KIconLoader::SizeSmallMedium)); m_busySeq->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); m_busySeq->setWidget(m_screenshotL); m_busySeq->start(); connect(m_screenshotL, SIGNAL(clicked()), this, SLOT(deleteLater())); } ScreenShotViewer::~ScreenShotViewer() { } void ScreenShotViewer::resultJob(KJob *job) { m_busySeq->stop(); auto fJob = qobject_cast(job); if (!fJob->error()) { m_screenshot = QPixmap(fJob->destUrl().toLocalFile()); QPropertyAnimation *anim1 = new QPropertyAnimation(this, "size"); anim1->setDuration(500); anim1->setStartValue(size()); anim1->setEndValue(m_screenshot.size()); anim1->setEasingCurve(QEasingCurve::OutCubic); connect(anim1, &QPropertyAnimation::finished, this, &ScreenShotViewer::fadeIn); anim1->start(); } else { m_screenshotL->setText(i18n("Could not find screen shot.")); } } void ScreenShotViewer::fadeIn() { auto effect = new QGraphicsOpacityEffect(m_screenshotL); effect->setOpacity(0); auto anim = new QPropertyAnimation(effect, "opacity"); anim->setDuration(500); anim->setStartValue(qreal(0)); anim->setEndValue(qreal(1)); m_screenshotL->setGraphicsEffect(effect); m_screenshotL->setPixmap(m_screenshot); m_screenshotL->adjustSize(); anim->start(); } diff --git a/Apper/ScreenShotViewer.h b/Apper/ScreenShotViewer.h index 7abe280..bde2b04 100644 --- a/Apper/ScreenShotViewer.h +++ b/Apper/ScreenShotViewer.h @@ -1,49 +1,49 @@ /*************************************************************************** - * Copyright (C) 2009-2010 by Daniel Nicoletti * + * Copyright (C) 2009-2018 by Daniel Nicoletti * * dantti12@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; see the file COPYING. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef SCREEN_SHOT_VIEWER_H #define SCREEN_SHOT_VIEWER_H #include #include #include #include class ClickableLabel; class ScreenShotViewer : public QScrollArea { Q_OBJECT public: - explicit ScreenShotViewer(const QString &url, QWidget *parent = 0); + explicit ScreenShotViewer(const QUrl &url, QWidget *parent = nullptr); ~ScreenShotViewer(); private Q_SLOTS: void resultJob(KJob *); void fadeIn(); private: KPixmapSequenceOverlayPainter *m_busySeq; QPixmap m_screenshot; ClickableLabel *m_screenshotL; }; #endif diff --git a/apperd/CMakeLists.txt b/apperd/CMakeLists.txt index 665b3c9..495f033 100644 --- a/apperd/CMakeLists.txt +++ b/apperd/CMakeLists.txt @@ -1,41 +1,41 @@ # CMakeLists for the kded component set(kded_apperd_SRCS DistroUpgrade.cpp DBusInterface.cpp TransactionJob.cpp TransactionWatcher.cpp RefreshCacheTask.cpp Updater.cpp RebootListener.cpp ApperdThread.cpp apperd.cpp ) qt5_add_dbus_adaptor(kded_apperd_SRCS org.kde.apperd.xml DBusInterface.h DBusInterface ) add_library(kded_apperd MODULE ${kded_apperd_SRCS}) target_link_libraries(kded_apperd KF5::WidgetsAddons KF5::KIOFileWidgets KF5::Notifications KF5::DBusAddons - ${PackageKitQt5_LIBRARIES} PW::KWorkspace + PK::packagekitqt5 apper_private ) if(DEBCONF_SUPPORT) target_link_libraries(kded_apperd DebconfKDE::Main) endif(DEBUCONF_SUPPORT) set_target_properties(kded_apperd PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/apper) install(TARGETS kded_apperd DESTINATION ${CMAKE_INSTALL_QTPLUGINDIR}) install(FILES apperd.notifyrc DESTINATION ${CMAKE_INSTALL_DATADIR}/apperd) install(FILES apperd.desktop DESTINATION ${CMAKE_INSTALL_KSERVICES5DIR}/kded) diff --git a/libapper/AppStream.cpp b/libapper/AppStream.cpp index eecc763..fb94a28 100644 --- a/libapper/AppStream.cpp +++ b/libapper/AppStream.cpp @@ -1,219 +1,240 @@ /*************************************************************************** * Copyright (C) 2010 by Daniel Nicoletti * * Copyright (C) 2012-2013 by Matthias Klumpp * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * 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 General Public License * * along with this program. If not, see . * ***************************************************************************/ #include #include +#include +#include +#include #include "AppStream.h" #include +#include -AppStream* AppStream::m_instance = 0; +Q_DECLARE_LOGGING_CATEGORY(APPER_LIB) -AppStream* AppStream::instance() +AppStreamHelper* AppStreamHelper::m_instance = 0; + +AppStreamHelper* AppStreamHelper::instance() { if(!m_instance) { - m_instance = new AppStream(qApp); + m_instance = new AppStreamHelper(qApp); m_instance->open(); } return m_instance; } -AppStream::AppStream(QObject *parent) +AppStreamHelper::AppStreamHelper(QObject *parent) : QObject(parent) { #ifdef HAVE_APPSTREAM // create new AppStream metadata pool - m_pool = APPSTREAMQT_(); + m_pool = new AppStream::Pool(this); #endif //HAVE_APPSTREAM } -AppStream::~AppStream() +AppStreamHelper::~AppStreamHelper() { -#ifdef HAVE_APPSTREAM - g_object_unref(m_pool); -#endif } -bool AppStream::open() +bool AppStreamHelper::open() { -#ifdef HAVE_APPSTREAM - g_autoptr(GError) error = NULL; - - as_pool_load (m_pool, NULL, &error); - if (error != NULL) { - qWarning("Unable to open AppStream metadata pool: %s", error->message); - return false; - } - - // cache application data (we should actually search the data directly via as_pool_search()...) - auto cptArray = as_pool_get_components(m_pool); - if (cptArray == NULL) { - qWarning("AppStream application array way NULL! (This should never happen)"); +#ifdef HAVE_APPSTREAM + QString error; + if (!m_pool->load(&error)) { + qCWarning(APPER_LIB) << "Unable to open AppStream metadata pool:" << error; return false; } - for (uint i = 0; i < cptArray->len; i++) { - auto cpt = AS_COMPONENT (g_ptr_array_index(cptArray, i)); - // we only want apps at time - auto cptKind = as_component_get_kind (cpt); - if ((cptKind != AS_COMPONENT_KIND_DESKTOP_APP) && - (cptKind != AS_COMPONENT_KIND_CONSOLE_APP) && - (cptKind != AS_COMPONENT_KIND_WEB_APP)) - continue; - - Application app; - // Application name - app.name = QString::fromUtf8(as_component_get_name(cpt)); - - // Package name - auto pkgnameC = as_component_get_pkgname(cpt); - QString pkgName; - if (pkgnameC != NULL) - pkgName = QString::fromUtf8(pkgnameC); - - // Desktop file - app.id = QString::fromUtf8(as_component_get_id(cpt)); - - // Summary - app.summary = QString::fromUtf8(as_component_get_summary(cpt)); - - // Description - app.description = QString::fromUtf8(as_component_get_description(cpt)); - - // Application stock icon - auto icons = as_component_get_icons(cpt); - for (uint i = 0; i < icons->len; i++) { - auto icon = AS_ICON (g_ptr_array_index (icons, i)); - if (as_icon_get_kind (icon) != AS_ICON_KIND_STOCK) - app.icon_url = QString::fromUtf8(as_icon_get_filename (icon)); +// // cache application data (we should actually search the data directly via as_pool_search()...) +// auto cptArray = as_pool_get_components(m_pool); +// if (cptArray == NULL) { +// qWarning("AppStream application array way NULL! (This should never happen)"); +// return false; +// } + +// for (uint i = 0; i < cptArray->len; i++) { +// auto cpt = AS_COMPONENT (g_ptr_array_index(cptArray, i)); +// // we only want apps at time +// auto cptKind = as_component_get_kind (cpt); +// if ((cptKind != AS_COMPONENT_KIND_DESKTOP_APP) && +// (cptKind != AS_COMPONENT_KIND_CONSOLE_APP) && +// (cptKind != AS_COMPONENT_KIND_WEB_APP)) +// continue; + +// Application app; +// // Application name +// app.name = QString::fromUtf8(as_component_get_name(cpt)); + +// // Package name +// auto pkgnameC = as_component_get_pkgname(cpt); +// QString pkgName; +// if (pkgnameC != NULL) +// pkgName = QString::fromUtf8(pkgnameC); + +// // Desktop file +// app.id = QString::fromUtf8(as_component_get_id(cpt)); + +// // Summary +// app.summary = QString::fromUtf8(as_component_get_summary(cpt)); + +// // Description +// app.description = QString::fromUtf8(as_component_get_description(cpt)); + +// // Application stock icon +// auto icons = as_component_get_icons(cpt); +// for (uint i = 0; i < icons->len; i++) { +// auto icon = AS_ICON (g_ptr_array_index (icons, i)); +// if (as_icon_get_kind (icon) != AS_ICON_KIND_STOCK) +// app.icon_url = QString::fromUtf8(as_icon_get_filename (icon)); +// } + +// // Application categories +// app.categories = QStringList(); +// auto cats = as_component_get_categories(cpt); +// for (uint i = 0; i < cats->len; i++) { +// auto category = (const gchar*) g_ptr_array_index (cats, i); +// app.categories << QString::fromUtf8(category); +// } + +// // add default screenshot urls +// auto scrs = as_component_get_screenshots (cpt); + +// // find default screenshot, if possible (otherwise we get a random one) +// AsScreenshot *scr = NULL; +// for (uint i = 0; i < scrs->len; i++) { +// scr = AS_SCREENSHOT (g_ptr_array_index (scrs, i)); +// if (as_screenshot_get_kind (scr) == AS_SCREENSHOT_KIND_DEFAULT) +// break; +// } + +// if (scr != NULL) { +// auto imgs = as_screenshot_get_images (scr); +// for (uint i = 0; i < imgs->len; i++) { +// auto img = AS_IMAGE (g_ptr_array_index (imgs, i)); +// if ((as_image_get_kind (img) == AS_IMAGE_KIND_SOURCE) && (app.screenshot.isEmpty())) { +// app.screenshot = QString::fromUtf8(as_image_get_url (img)); +// } else if ((as_image_get_kind (img) == AS_IMAGE_KIND_THUMBNAIL) && (app.thumbnail.isEmpty())) { +// app.thumbnail = QString::fromUtf8(as_image_get_url (img)); +// } + +// if ((!app.screenshot.isEmpty()) && (!app.thumbnail.isEmpty())) +// break; +// } +// } + +// m_appInfo.insertMulti(pkgName, app); +// } + + const QList apps = m_pool->componentsByKind(AppStream::Component::KindDesktopApp); + for (const AppStream::Component &app : apps) { + const QStringList pkgNames = app.packageNames(); + for (const QString &pkgName : pkgNames) { + m_appInfo.insertMulti(pkgName, app); } - - // Application categories - app.categories = QStringList(); - auto cats = as_component_get_categories(cpt); - for (uint i = 0; i < cats->len; i++) { - auto category = (const gchar*) g_ptr_array_index (cats, i); - app.categories << QString::fromUtf8(category); - } - - // add default screenshot urls - auto scrs = as_component_get_screenshots (cpt); - - // find default screenshot, if possible (otherwise we get a random one) - AsScreenshot *scr = NULL; - for (uint i = 0; i < scrs->len; i++) { - scr = AS_SCREENSHOT (g_ptr_array_index (scrs, i)); - if (as_screenshot_get_kind (scr) == AS_SCREENSHOT_KIND_DEFAULT) - break; - } - - if (scr != NULL) { - auto imgs = as_screenshot_get_images (scr); - for (uint i = 0; i < imgs->len; i++) { - auto img = AS_IMAGE (g_ptr_array_index (imgs, i)); - if ((as_image_get_kind (img) == AS_IMAGE_KIND_SOURCE) && (app.screenshot.isEmpty())) { - app.screenshot = QString::fromUtf8(as_image_get_url (img)); - } else if ((as_image_get_kind (img) == AS_IMAGE_KIND_THUMBNAIL) && (app.thumbnail.isEmpty())) { - app.thumbnail = QString::fromUtf8(as_image_get_url (img)); - } - - if ((!app.screenshot.isEmpty()) && (!app.thumbnail.isEmpty())) - break; - } - } - - m_appInfo.insertMulti(pkgName, app); } return true; #else return false; #endif } -QList AppStream::applications(const QString &pkgName) const +QList AppStreamHelper::applications(const QString &pkgName) const { return m_appInfo.values(pkgName); } -QString AppStream::genericIcon(const QString &pkgName) const +QString AppStreamHelper::genericIcon(const QString &pkgName) const { if (m_appInfo.contains(pkgName)) { - foreach (const Application &app, applications(pkgName)) { - if (!app.icon_url.isEmpty()) { - return app.icon_url; - } - } +// const QList apps = applications(pkgName); +// for (const AppStream::Component &app : apps) { +// if (!app.icon_url.isEmpty()) { +// return app.icon_url; +// } +// } } return QString(); } -QStringList AppStream::findPkgNames(const CategoryMatcher &parser) const +QStringList AppStreamHelper::findPkgNames(const CategoryMatcher &parser) const { QStringList packages; - QHash::const_iterator i = m_appInfo.constBegin(); - while (i != m_appInfo.constEnd()) { - if (parser.match(i.value().categories)) { -// kDebug() << i.key() << categories; - packages << i.key(); - } - ++i; - } +// QHash::const_iterator i = m_appInfo.constBegin(); +// while (i != m_appInfo.constEnd()) { +// if (parser.match(i.value().categories)) { +//// kDebug() << i.key() << categories; +// packages << i.key(); +// } +// ++i; +// } return packages; } -QString AppStream::thumbnail(const QString &pkgName) const +QString AppStreamHelper::thumbnail(const QString &pkgName) const { #ifdef HAVE_APPSTREAM - QString url = ""; + QString url = QLatin1String(""); if (m_appInfo.contains(pkgName)) { - Application app = m_appInfo.value(pkgName); - url = app.thumbnail; + AppStream::Component app = m_appInfo.value(pkgName); +// ) +// url = app.icon(); } return url; #else Q_UNUSED(pkgName) return QString(); #endif //HAVE_APPSTREAM } -QString AppStream::screenshot(const QString &pkgName) const +QUrl AppStreamHelper::screenshot(const QString &pkgName) const { + QUrl url; #ifdef HAVE_APPSTREAM - QString url = ""; if (m_appInfo.contains(pkgName)) { - Application app = m_appInfo.value(pkgName); - url = app.screenshot; + AppStream::Component app = m_appInfo.value(pkgName); + const QList screenshots = app.screenshots(); + for (const AppStream::Screenshot &screenshot : screenshots) { + const QList images = screenshot.images(); + for (const AppStream::Image &image : images) { + url = image.url(); + break; + } + + if (screenshot.isDefault() && !url.isEmpty()) { + break; + } + } } - return url; #else Q_UNUSED(pkgName) - return QString(); #endif //HAVE_APPSTREAM + return url; } diff --git a/libapper/AppStream.h b/libapper/AppStream.h index f7e68d2..0f87ca4 100644 --- a/libapper/AppStream.h +++ b/libapper/AppStream.h @@ -1,65 +1,59 @@ /*************************************************************************** * Copyright (C) 2010 by Daniel Nicoletti * * Copyright (C) 2012-2013 by Matthias Klumpp * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * 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 General Public License * * along with this program. If not, see . * ***************************************************************************/ #ifndef APPSTREAM_H #define APPSTREAM_H #include "CategoryMatcher.h" +#include + #include #include -struct _AsPool; +namespace AppStream { +class Pool; +} struct _AsScreenshotService; typedef struct _AsScreenshotService AsScreenshotService; -class Q_DECL_EXPORT AppStream : public QObject { +class Q_DECL_EXPORT AppStreamHelper : public QObject { public: - struct Application { - QString name; - QString summary; - QString description; - QString icon_url; - QString id; - QStringList categories; - QString screenshot; - QString thumbnail; - }; - static AppStream* instance(); - virtual ~AppStream(); + static AppStreamHelper* instance(); + virtual ~AppStreamHelper(); bool open(); - QList applications(const QString &pkgName) const; + QList applications(const QString &pkgName) const; QString genericIcon(const QString &pkgName) const; QStringList findPkgNames(const CategoryMatcher &parser) const; QString thumbnail(const QString &pkgName) const; - QString screenshot(const QString &pkgName) const; + QUrl screenshot(const QString &pkgName) const; private: - explicit AppStream(QObject *parent = 0); - _AsPool *m_pool; + explicit AppStreamHelper(QObject *parent = 0); + AppStream::Pool *m_pool; - QHash m_appInfo; - static AppStream *m_instance; + QHash m_appInfo; + static AppStreamHelper *m_instance; }; #endif // APPSTREAM_H diff --git a/libapper/CMakeLists.txt b/libapper/CMakeLists.txt index b0c8685..6581778 100644 --- a/libapper/CMakeLists.txt +++ b/libapper/CMakeLists.txt @@ -1,72 +1,65 @@ # CMakeLists for Apper private shared library add_definitions(-DTRANSLATION_DOMAIN=\"apper\") # Set the correct compiler options IF(CMAKE_SIZEOF_VOID_P EQUAL 4) # 32 bit MESSAGE(STATUS "Apper detected that you use a 32 bit processor.") ELSE(CMAKE_SIZEOF_VOID_P EQUAL 4) # 64 bit (well, anything else than 32 bit, but someone use something else than 32 or 64 bit ?) set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") MESSAGE(STATUS "Apper detected that you use a 64 bit processor. Added -fPIC to the CXX_FLAGS.") ENDIF(CMAKE_SIZEOF_VOID_P EQUAL 4) set(libapper_SRCS CategoryMatcher.cpp PkIcons.cpp PkStrings.cpp ApplicationLauncher.cpp ApplicationsDelegate.cpp ApplicationSortFilterModel.cpp CategoryDrawer.cpp ChangesDelegate.cpp TransactionDelegate.cpp PkTransaction.cpp PkTransactionWidget.cpp PkTransactionProgressModel.cpp RepoSig.cpp LicenseAgreement.cpp PackageModel.cpp CustomProgressBar.cpp Requirements.cpp PackageImportance.cpp CategorizedView.cpp InfoWidget.cpp ) if(APPSTREAM) -# pkg_check_modules(GLIB2 REQUIRED glib-2.0>=2.36) -# pkg_check_modules(APPSTREAM REQUIRED appstream>=0.10.0) find_package(AppStreamQt REQUIRED) -# set(libapper_SRCS ${libapper_SRCS} AppStream.cpp) + set(libapper_SRCS ${libapper_SRCS} AppStream.cpp) endif() ki18n_wrap_ui(libapper_SRCS ApplicationLauncher.ui PkTransactionWidget.ui RepoSig.ui LicenseAgreement.ui Requirements.ui InfoWidget.ui ) add_library(apper_private SHARED ${libapper_SRCS}) -include_directories(${CMAKE_CURRENT_BINARY_DIR} - ${GLIB2_INCLUDE_DIR} - ${APPSTREAM_INCLUDE_DIRS} -) - target_link_libraries(apper_private KF5::WidgetsAddons KF5::KIOFileWidgets KF5::IconThemes KF5::I18n Qt5::Core - ${PackageKitQt5_LIBRARIES} - ${APPSTREAM_LIBRARIES} + PK::packagekitqt5 + AppStreamQt ) install(TARGETS apper_private DESTINATION ${CMAKE_INSTALL_LIBDIR}/apper) diff --git a/libapper/PackageModel.cpp b/libapper/PackageModel.cpp index 9f63ff5..f343946 100644 --- a/libapper/PackageModel.cpp +++ b/libapper/PackageModel.cpp @@ -1,907 +1,919 @@ /*************************************************************************** * Copyright (C) 2008-2018 by Daniel Nicoletti * * dantti12@gmail.com * * Copyright (C) 2008 by Trever Fischer * * wm161@wm161.net * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; see the file COPYING. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * ***************************************************************************/ #include #include "PackageModel.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_APPSTREAM +#include #include #endif #define ICON_SIZE 22 #define OVERLAY_SIZE 16 using namespace PackageKit; +Q_DECLARE_LOGGING_CATEGORY(APPER_LIB) + PackageModel::PackageModel(QObject *parent) : QAbstractItemModel(parent), m_finished(false), m_checkable(false), m_fetchSizesTransaction(0), m_fetchInstalledVersionsTransaction(0) { m_installedEmblem = PkIcons::getIcon(QLatin1String("dialog-ok-apply"), QString()).pixmap(16, 16); m_roles[SortRole] = "rSort"; m_roles[NameRole] = "rName"; m_roles[SummaryRole] = "rSummary"; m_roles[VersionRole] = "rVersion"; m_roles[ArchRole] = "rArch"; m_roles[IconRole] = "rIcon"; m_roles[IdRole] = "rId"; m_roles[CheckStateRole] = "rChecked"; m_roles[InfoRole] = "rInfo"; m_roles[ApplicationId] = "rApplicationId"; m_roles[IsPackageRole] = "rIsPackageRole"; m_roles[PackageName] = "rPackageName"; m_roles[InfoIconRole] = "rInfoIcon"; } void PackageModel::addSelectedPackagesFromModel(PackageModel *model) { const QList packages = model->internalSelectedPackages(); for (const InternalPackage &package : packages) { addPackage(package.info, package.packageID, package.summary, true); } finished(); } void PackageModel::addNotSelectedPackage(Transaction::Info info, const QString &packageID, const QString &summary) { addPackage(info, packageID, summary); } void PackageModel::addPackage(Transaction::Info info, const QString &packageID, const QString &summary, bool selected) { if (m_finished) { qDebug() << Q_FUNC_INFO << "we are finished calling clear"; clear(); } switch(info) { case Transaction::InfoBlocked: case Transaction::InfoFinished: case Transaction::InfoCleanup: return; default: break; } #ifdef HAVE_APPSTREAM - QList applications; + QList applications; if (!m_checkable) { const QString packageName = Transaction::packageName(packageID); -// applications = AppStream::instance()->applications(packageName); + applications = AppStreamHelper::instance()->applications(packageName); - for (const AppStream::Application &app : applications) { + for (const AppStream::Component &app : applications) { InternalPackage iPackage; iPackage.info = info; iPackage.packageID = packageID; iPackage.pkgName = packageName; iPackage.version = Transaction::packageVersion(packageID); iPackage.arch = Transaction::packageArch(packageID); iPackage.repo = Transaction::packageData(packageID); iPackage.isPackage = false; - if (app.name.isEmpty()) { + if (app.name().isEmpty()) { iPackage.displayName = packageName; } else { - iPackage.displayName = app.name; + iPackage.displayName = app.name(); } - if (app.summary.isEmpty()) { + if (app.summary().isEmpty()) { iPackage.summary = summary; } else { - iPackage.summary = app.summary; + iPackage.summary = app.summary(); + } + + const QList icons = app.icons(); + for (const AppStream::Icon &icon : icons) { + if (icon.url().isEmpty()) { + iPackage.icon = icon.name(); + } else { + iPackage.icon = icon.url().toLocalFile(); + } + break; } - iPackage.icon = app.icon_url; - iPackage.appId = app.id; + iPackage.appId = app.id(); iPackage.size = 0; if (selected) { checkPackage(iPackage, false); } m_packages.append(iPackage); } } if (applications.isEmpty()) { #endif //HAVE_APPSTREAM InternalPackage iPackage; iPackage.info = info; iPackage.packageID = packageID; iPackage.pkgName = Transaction::packageName(packageID); iPackage.displayName = iPackage.pkgName; iPackage.version = Transaction::packageVersion(packageID); iPackage.arch = Transaction::packageArch(packageID); iPackage.repo = Transaction::packageData(packageID); iPackage.summary = summary; #ifdef HAVE_APPSTREAM -// iPackage.icon = AppStream::instance()->genericIcon(iPackage.pkgName); + iPackage.icon = AppStreamHelper::instance()->genericIcon(iPackage.pkgName); if (m_checkable) { // in case of updates model only check if it's an app -// applications = AppStream::instance()->applications(iPackage.pkgName); + applications = AppStreamHelper::instance()->applications(iPackage.pkgName); if (!applications.isEmpty()) { iPackage.isPackage = false; } } #endif // HAVE_APPSTREAM if (selected) { checkPackage(iPackage, false); } m_packages.append(iPackage); #ifdef HAVE_APPSTREAM } #endif // HAVE_APPSTREAM } void PackageModel::addSelectedPackage(Transaction::Info info, const QString &packageID, const QString &summary) { addPackage(info, packageID, summary, true); } QVariant PackageModel::headerData(int section, Qt::Orientation orientation, int role) const { QVariant ret; if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch (section) { case NameCol: if (m_checkable) { ret = PkStrings::packageQuantity(true, m_packages.size(), m_checkedPackages.size()); } else { ret = i18n("Name"); } break; case VersionCol: ret = i18n("Version"); break; case CurrentVersionCol: ret = i18n("Installed Version"); break; case ArchCol: ret = i18n("Arch"); break; case OriginCol: ret = i18n("Origin"); break; case SizeCol: ret = i18n("Size"); break; case ActionCol: ret = i18n("Action"); break; } } return ret; } int PackageModel::rowCount(const QModelIndex &parent) const { if (parent.isValid() || !m_finished) { return 0; } return m_packages.size(); } QModelIndex PackageModel::index(int row, int column, const QModelIndex &parent) const { // kDebug() << parent.isValid() << m_packageCount << row << column; // Check to see if the index isn't out of list if (!parent.isValid() && m_packages.size() > row) { return createIndex(row, column); } return QModelIndex(); } QModelIndex PackageModel::parent(const QModelIndex &index) const { Q_UNUSED(index) return QModelIndex(); } QHash PackageModel::roleNames() const { return m_roles; } QVariant PackageModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } const InternalPackage &package = m_packages[index.row()]; if (index.column() == NameCol) { switch (role) { case Qt::CheckStateRole: if (!m_checkable) { return QVariant(); } if (containsChecked(package.packageID)) { return Qt::Checked; } return Qt::Unchecked; case CheckStateRole: if (containsChecked(package.packageID)) { return Qt::Checked; } return Qt::Unchecked; case IsPackageRole: return package.isPackage; case Qt::DisplayRole: return package.displayName; case Qt::DecorationRole: { QPixmap icon = QPixmap(44, ICON_SIZE); icon.fill(Qt::transparent); if (!package.icon.isNull()) { QPixmap pixmap; if (package.icon.startsWith(QLatin1String("/"))) { pixmap = QPixmap(); pixmap.load(package.icon); pixmap = pixmap.scaledToHeight(ICON_SIZE); } else { pixmap = KIconLoader::global()->loadIcon(package.icon, KIconLoader::NoGroup, ICON_SIZE, KIconLoader::DefaultState, QStringList(), 0L, true); } if (!pixmap.isNull()) { QPainter painter(&icon); painter.drawPixmap(QPoint(2, 0), pixmap); } } if (package.info == Transaction::InfoInstalled || package.info == Transaction::InfoCollectionInstalled) { QPainter painter(&icon); QPoint startPoint; // bottom right corner startPoint = QPoint(44 - OVERLAY_SIZE, 4); painter.drawPixmap(startPoint, m_installedEmblem); } else if (m_checkable) { QIcon emblemIcon = PkIcons::packageIcon(package.info); QPainter painter(&icon); QPoint startPoint; // bottom right corner startPoint = QPoint(44 - OVERLAY_SIZE, 4); painter.drawPixmap(startPoint, emblemIcon.pixmap(OVERLAY_SIZE, OVERLAY_SIZE)); } return icon; } case PackageName: return package.pkgName; case Qt::ToolTipRole: if (m_checkable) { return PkStrings::info(package.info); } else { return i18n("Version: %1\nArchitecture: %2", package.version, package.arch); } } } else if (role == Qt::DisplayRole) { if (index.column() == VersionCol) { return package.version; } else if (index.column() == CurrentVersionCol) { return package.currentVersion; } else if (index.column() == ArchCol) { return package.arch; } else if (index.column() == OriginCol) { return package.repo; } else if (index.column() == SizeCol) { KFormat f; return package.size ? f.formatByteSize(package.size) : QString(); } } else if (index.column() == SizeCol && role == Qt::TextAlignmentRole) { return static_cast(Qt::AlignRight | Qt::AlignVCenter); } switch (role) { case IconRole: return package.icon; case SortRole: return QString(package.displayName % QLatin1Char(' ') % package.version % QLatin1Char(' ') % package.arch); case CheckStateRole: if (containsChecked(package.packageID)) { return Qt::Checked; } return Qt::Unchecked; case IdRole: return package.packageID; case NameRole: return package.displayName; case SummaryRole: return package.summary; case VersionRole: return package.version; case ArchRole: return package.arch; case OriginCol: return package.repo; case InfoRole: return qVariantFromValue(package.info); case KCategorizedSortFilterProxyModel::CategoryDisplayRole: if (package.info == Transaction::InfoInstalled || package.info == Transaction::InfoCollectionInstalled) { return i18n("To be Removed"); } else { return i18n("To be Installed"); } case KCategorizedSortFilterProxyModel::CategorySortRole: // USING 0 here seems to let things unsorted return package.isPackage ? 1 : 0; // Packages comes after applications case ApplicationId: return package.appId; case InfoIconRole: return PkIcons::packageIcon(package.info); default: return QVariant(); } return QVariant(); } bool PackageModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (role == Qt::CheckStateRole && m_packages.size() > index.row()) { if (value.toBool()) { checkPackage(m_packages[index.row()]); } else { uncheckPackage(m_packages[index.row()].packageID); } emit changed(!m_checkedPackages.isEmpty()); return true; } return false; } Qt::ItemFlags PackageModel::flags(const QModelIndex &index) const { if (index.column() == NameCol) { return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | QAbstractItemModel::flags(index); } return QAbstractItemModel::flags(index); } int PackageModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); if (m_checkable) { // when the model is checkable the action column is not shown return ActionCol; } else { return ActionCol + 1; } } void PackageModel::removePackage(const QString &packageID) { int i = 0; while (i < m_packages.size()) { InternalPackage iPackage = m_packages[i]; if (iPackage.packageID == packageID && iPackage.info != Transaction::InfoUntrusted) { beginRemoveRows(QModelIndex(), i, i); m_packages.remove(i); endRemoveRows(); // since we removed one entry we don't // need to increase the counter continue; } ++i; } } void PackageModel::checkAll() { m_checkedPackages.clear(); for (const InternalPackage &package : qAsConst(m_packages)) { checkPackage(package, false); } emit dataChanged(createIndex(0, 0), createIndex(m_packages.size(), 0)); emit changed(!m_checkedPackages.isEmpty()); } void PackageModel::clear() { qDebug() << Q_FUNC_INFO; beginRemoveRows(QModelIndex(), 0, m_packages.size()); m_finished = false; m_packages.clear(); m_fetchSizesTransaction = 0; m_fetchInstalledVersionsTransaction = 0; if (m_getUpdatesTransaction) { m_getUpdatesTransaction->disconnect(this); m_getUpdatesTransaction->cancel(); } endRemoveRows(); } void PackageModel::clearSelectedNotPresent() { auto it = m_checkedPackages.begin(); while (it != m_checkedPackages.end()) { const InternalPackage &package = it.value(); bool notFound = true; for (const InternalPackage &iPackage : qAsConst(m_packages)) { if (iPackage.packageID == package.packageID) { notFound = false; break; } } if (notFound) { // Uncheck the package If it's not in the model m_checkedPackages.erase(it); uncheckPackageLogic(package.packageID); } else { ++it; } } } bool PackageModel::checkable() const { return m_checkable; } void PackageModel::uncheckInstalledPackages() { auto it = m_checkedPackages.begin(); while (it != m_checkedPackages.end()) { const InternalPackage &package = it.value(); if (package.info == Transaction::InfoInstalled || package.info == Transaction::InfoCollectionInstalled) { const QString pkgId = it.key(); it = m_checkedPackages.erase(it); uncheckPackageLogic(pkgId, true); } else { ++it; } } } void PackageModel::uncheckAvailablePackages() { auto it = m_checkedPackages.begin(); while (it != m_checkedPackages.end()) { const InternalPackage &package = it.value(); if (package.info == Transaction::InfoAvailable || package.info == Transaction::InfoCollectionAvailable) { const QString pkgId = it.key(); it = m_checkedPackages.erase(it); uncheckPackageLogic(pkgId, true); } else { ++it; } } } void PackageModel::finished() { auto trans = qobject_cast(sender()); qDebug() << Q_FUNC_INFO << trans << sender(); if (trans /*== m_getUpdatesTransaction*/) { // m_getUpdatesTransaction = 0; // When pkd dies this method is called twice // pk-qt2 bug.. disconnect(trans, &Transaction::finished, this, &PackageModel::finished); } // The whole structure is about to change if (!m_packages.isEmpty()) { beginInsertRows(QModelIndex(), 0, m_packages.size() - 1); m_finished = true; endInsertRows(); } emit changed(!m_checkedPackages.isEmpty()); } void PackageModel::fetchSizes() { if (m_fetchSizesTransaction) { return; } // get package size QStringList pkgs; for (const InternalPackage &p : qAsConst(m_packages)) { pkgs << p.packageID; } if (!pkgs.isEmpty()) { m_fetchSizesTransaction = Daemon::getDetails(pkgs); connect(m_fetchSizesTransaction, &Transaction::details, this, &PackageModel::updateSize); connect(m_fetchSizesTransaction, &Transaction::finished, this, &PackageModel::fetchSizesFinished); } } void PackageModel::fetchSizesFinished() { auto trans = qobject_cast(sender()); if (trans) { // When pkd dies this method is called twice // pk-qt2 bug.. disconnect(trans, &Transaction::finished, this, &PackageModel::fetchSizesFinished); } // emit this after all is changed otherwise on large models it will // be hell slow... emit dataChanged(createIndex(0, SizeCol), createIndex(m_packages.size(), SizeCol)); emit changed(!m_checkedPackages.isEmpty()); } void PackageModel::updateSize(const PackageKit::Details &details) { // if size is 0 don't waste time looking for the package qulonglong size = details.size(); if (size == 0) { return; } for (int i = 0; i < m_packages.size(); ++i) { const QString packageId = details.packageId(); if (packageId == m_packages[i].packageID) { m_packages[i].size = size; if (m_checkable) { // updates the checked packages as well if (m_checkedPackages.contains(packageId)) { // Avoid checking packages that aren't checked m_checkedPackages[packageId].size = size; } break; } #ifdef HAVE_APPSTREAM if (m_checkable) { // checkable models don't have duplicated package ids // so don't waste time scanning all list break; } #else // Without AppStream we don't have duplicated package ids break; #endif // HAVE_APPSTREAM } } } void PackageModel::fetchCurrentVersions() { if (m_fetchInstalledVersionsTransaction) { return; } // get package current version QStringList pkgs; for (const InternalPackage &p : qAsConst(m_packages)) { pkgs << p.pkgName; } if (!pkgs.isEmpty()) { m_fetchInstalledVersionsTransaction = Daemon::resolve(pkgs, Transaction::FilterInstalled);; connect(m_fetchInstalledVersionsTransaction, &Transaction::package, this, &PackageModel::updateCurrentVersion); connect(m_fetchInstalledVersionsTransaction, &Transaction::finished, this, &PackageModel::fetchCurrentVersionsFinished); } } void PackageModel::fetchCurrentVersionsFinished() { auto trans = qobject_cast(sender()); if (trans) { // When pkd dies this method is called twice // pk-qt2 bug.. disconnect(trans, &Transaction::finished, this, &PackageModel::fetchCurrentVersionsFinished); } // emit this after all is changed otherwise on large models it will // be hell slow... emit dataChanged(createIndex(0, CurrentVersionCol), createIndex(m_packages.size(), CurrentVersionCol)); emit changed(!m_checkedPackages.isEmpty()); } void PackageModel::updateCurrentVersion(Transaction::Info info, const QString &packageID, const QString &summary) { Q_UNUSED(info) Q_UNUSED(summary) // if current version is empty don't waste time looking if (!Transaction::packageVersion(packageID).isEmpty()) { for (int i = 0; i < m_packages.size(); ++i) { if (Transaction::packageName(packageID) == m_packages[i].pkgName && Transaction::packageArch(packageID) == m_packages[i].arch) { m_packages[i].currentVersion = Transaction::packageVersion(packageID); if (m_checkable) { // updates the checked packages as well if (m_checkedPackages.contains(m_packages[i].packageID)) { // Avoid checking packages that aren't checked m_checkedPackages[m_packages[i].packageID].currentVersion = Transaction::packageVersion(packageID); } break; } } } } } void PackageModel::getUpdates(bool fetchCurrentVersions, bool selected) { clear(); m_getUpdatesTransaction = Daemon::getUpdates(); if (selected) { connect(m_getUpdatesTransaction, &Transaction::package, this, &PackageModel::addSelectedPackage); } else { connect(m_getUpdatesTransaction, &Transaction::package, this, &PackageModel::addNotSelectedPackage); } // connect(m_getUpdatesTransaction, SIGNAL(finished(PackageKit::Transaction::Exit,uint)), // m_busySeq, SLOT(stop())); // connect(m_getUpdatesTransaction, SIGNAL(finished(PackageKit::Transaction::Exit,uint)), // this, SLOT(finished())); // This is required to estimate download size connect(m_getUpdatesTransaction, &Transaction::finished, this, &PackageModel::fetchSizes); if (fetchCurrentVersions) { connect(m_getUpdatesTransaction, &Transaction::finished, this, &PackageModel::fetchCurrentVersions); } connect(m_getUpdatesTransaction, SIGNAL(finished(PackageKit::Transaction::Exit,uint)), this, SLOT(getUpdatesFinished())); // get all updates } void PackageModel::toggleSelection(const QString &packageID) { if (containsChecked(packageID)) { uncheckPackage(packageID, true); } else { for (const InternalPackage &package : qAsConst(m_packages)) { if (package.packageID == packageID) { checkPackage(package); break; } } } } QString PackageModel::selectionStateText() const { return headerData(NameCol, Qt::Horizontal).toString(); } bool PackageModel::hasChanges() const { return !m_checkedPackages.isEmpty(); } int PackageModel::countInfo(PackageKit::Transaction::Info info) const { int ret = 0; for (const InternalPackage &package : qAsConst(m_packages)) { if (package.info == info) { ++ret; } } return ret; } void PackageModel::checkPackage(const InternalPackage &package, bool emitDataChanged) { QString pkgId = package.packageID; if (!containsChecked(pkgId)) { m_checkedPackages[pkgId] = package; // A checkable model does not have duplicated entries if (emitDataChanged || !m_checkable || !m_packages.isEmpty()) { // This is a slow operation so in case the user // is unchecking all of the packages there is // no need to emit data changed for every item for (int i = 0; i < m_packages.size(); ++i) { if (m_packages[i].packageID == pkgId) { QModelIndex index = createIndex(i, 0); emit dataChanged(index, index); } } // The model might not be displayed yet if (m_finished) { emit changed(!m_checkedPackages.isEmpty()); } } } } void PackageModel::uncheckAll() { auto it = m_checkedPackages.begin(); while (it != m_checkedPackages.end()) { const QString pkgId = it.key(); it = m_checkedPackages.erase(it); uncheckPackageLogic(pkgId, true, false); } emit dataChanged(createIndex(0, 0), createIndex(m_packages.size(), 0)); emit changed(!m_checkedPackages.isEmpty()); } void PackageModel::uncheckPackageDefault(const QString &packageID) { uncheckPackage(packageID); } void PackageModel::uncheckPackage(const QString &packageID, bool forceEmitUnchecked, bool emitDataChanged) { auto it = m_checkedPackages.find(packageID); if (it != m_checkedPackages.end()) { m_checkedPackages.erase(it); uncheckPackageLogic(packageID, forceEmitUnchecked, emitDataChanged); } } void PackageModel::uncheckPackageLogic(const QString &packageID, bool forceEmitUnchecked, bool emitDataChanged) { if (forceEmitUnchecked || sender() == 0) { // The package might be removed by rmSelectedPackage // If we don't copy it the browse model won't uncheck there // right package emit packageUnchecked(packageID); } if (emitDataChanged || !m_checkable) { // This is a slow operation so in case the user // is unchecking all of the packages there is // no need to emit data changed for every item for (int i = 0; i < m_packages.size(); ++i) { if (m_packages[i].packageID == packageID) { QModelIndex index = createIndex(i, 0); emit dataChanged(index, index); } } // The model might not be displayed yet if (m_finished) { emit changed(!m_checkedPackages.isEmpty()); } } } QList PackageModel::internalSelectedPackages() const { QList ret; QHash::const_iterator i = m_checkedPackages.constBegin(); while (i != m_checkedPackages.constEnd()) { ret << i.value(); ++i; } return ret; } bool PackageModel::containsChecked(const QString &pid) const { return m_checkedPackages.contains(pid); } void PackageModel::setAllChecked(bool checked) { if (checked) { checkAll(); } else { uncheckAll(); } } QStringList PackageModel::selectedPackagesToInstall() const { QStringList list; for (const InternalPackage &package : qAsConst(m_checkedPackages)) { if (package.info != Transaction::InfoInstalled && package.info != Transaction::InfoCollectionInstalled) { // append the packages are not installed list << package.packageID; } } return list; } QStringList PackageModel::selectedPackagesToRemove() const { QStringList list; for (const InternalPackage &package : qAsConst(m_checkedPackages)) { if (package.info == Transaction::InfoInstalled || package.info == Transaction::InfoCollectionInstalled) { // check what packages are installed and marked to be removed list << package.packageID; } } return list; } QStringList PackageModel::packagesWithInfo(Transaction::Info info) const { QStringList list; for (const InternalPackage &package : qAsConst(m_packages)) { if (package.info == info) { // Append to the list if the package matches the info value list << package.packageID; } } return list; } QStringList PackageModel::packageIDs() const { QStringList list; for (const InternalPackage &package : qAsConst(m_packages)) { list << package.packageID; } return list; } unsigned long PackageModel::downloadSize() const { unsigned long size = 0; for (const InternalPackage &package : qAsConst(m_checkedPackages)) { size += package.size; } return size; } bool PackageModel::allSelected() const { for (const InternalPackage &package : qAsConst(m_packages)) { if (!containsChecked(package.packageID)) { return false; } } return true; } void PackageModel::setCheckable(bool checkable) { m_checkable = checkable; }