File indexing completed on 2022-09-27 16:24:30

0001 /***************************************************************************
0002  *   Copyright (C) 2009-2018 by Daniel Nicoletti                           *
0003  *   dantti12@gmail.com                                                    *
0004  *                                                                         *
0005  *   This program is free software; you can redistribute it and/or modify  *
0006  *   it under the terms of the GNU General Public License as published by  *
0007  *   the Free Software Foundation; either version 2 of the License, or     *
0008  *   (at your option) any later version.                                   *
0009  *                                                                         *
0010  *   This program is distributed in the hope that it will be useful,       *
0011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0013  *   GNU General Public License for more details.                          *
0014  *                                                                         *
0015  *   You should have received a copy of the GNU General Public License     *
0016  *   along with this program; see the file COPYING. If not, write to       *
0017  *   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,  *
0018  *   Boston, MA 02110-1301, USA.                                           *
0019  ***************************************************************************/
0020 
0021 #include "PackageDetails.h"
0022 #include "ui_PackageDetails.h"
0023 
0024 #include "ScreenShotViewer.h"
0025 
0026 #include <PackageModel.h>
0027 #include <PkStrings.h>
0028 #include <PkIcons.h>
0029 
0030 #include <KMessageBox>
0031 
0032 #include <KSycocaEntry>
0033 #include <KIconLoader>
0034 #include <KService>
0035 #include <KServiceGroup>
0036 #include <KDesktopFile>
0037 #include <QTemporaryFile>
0038 #include <KPixmapSequence>
0039 #include <QTextDocument>
0040 #include <QPlainTextEdit>
0041 #include <QScrollBar>
0042 #include <QPainter>
0043 #include <QAbstractAnimation>
0044 #include <QStringBuilder>
0045 
0046 #include <KFormat>
0047 #include <KIO/Job>
0048 #include <QMenu>
0049 #include <QDir>
0050 
0051 #include <QLoggingCategory>
0052 
0053 #include <Daemon>
0054 #include <Transaction>
0055 
0056 #include <config.h>
0057 
0058 #ifdef HAVE_APPSTREAM
0059 #include <AppStream.h>
0060 #endif
0061 
0062 #include "GraphicsOpacityDropShadowEffect.h"
0063 
0064 #define BLUR_RADIUS 15
0065 #define FINAL_HEIGHT 210
0066 
0067 using namespace PackageKit;
0068 
0069 Q_DECLARE_LOGGING_CATEGORY(APPER)
0070 
0071 Q_DECLARE_METATYPE(KPixmapSequenceOverlayPainter**)
0072 
0073 PackageDetails::PackageDetails(QWidget *parent)
0074  : QWidget(parent),
0075    ui(new Ui::PackageDetails),
0076    m_busySeq(nullptr),
0077    m_display(false),
0078    m_hideVersion(false),
0079    m_hideArch(false),
0080    m_transaction(nullptr),
0081    m_hasDetails(false),
0082    m_hasFileList(false)
0083 {
0084     ui->setupUi(this);
0085     ui->hideTB->setIcon(QIcon::fromTheme(QLatin1String("window-close")));
0086     connect(ui->hideTB, SIGNAL(clicked()), this, SLOT(hide()));
0087 
0088     auto menu = new QMenu(i18n("Display"), this);
0089     m_actionGroup = new QActionGroup(this);
0090 
0091     // we check to see which roles are supported by the backend
0092     // if so we ask for information and create the containers
0093     descriptionAction = menu->addAction(i18n("Description"));
0094     descriptionAction->setCheckable(true);
0095     descriptionAction->setData(PackageKit::Transaction::RoleGetDetails);
0096     m_actionGroup->addAction(descriptionAction);
0097     ui->descriptionW->setWidgetResizable(true);
0098 
0099     dependsOnAction = menu->addAction(i18n("Depends On"));
0100     dependsOnAction->setCheckable(true);
0101     dependsOnAction->setData(PackageKit::Transaction::RoleDependsOn);
0102     m_actionGroup->addAction(dependsOnAction);
0103     // Sets a transparent background
0104     QWidget *dependsViewport = ui->dependsOnLV->viewport();
0105     QPalette dependsPalette = dependsViewport->palette();
0106     dependsPalette.setColor(dependsViewport->backgroundRole(), Qt::transparent);
0107     dependsPalette.setColor(dependsViewport->foregroundRole(), dependsPalette.color(QPalette::WindowText));
0108     dependsViewport->setPalette(dependsPalette);
0109 
0110     m_dependsModel = new PackageModel(this);
0111     m_dependsProxy = new QSortFilterProxyModel(this);
0112     m_dependsProxy->setDynamicSortFilter(true);
0113     m_dependsProxy->setSortRole(PackageModel::SortRole);
0114     m_dependsProxy->setSourceModel(m_dependsModel);
0115     ui->dependsOnLV->setModel(m_dependsProxy);
0116     ui->dependsOnLV->sortByColumn(0, Qt::AscendingOrder);
0117     ui->dependsOnLV->header()->setDefaultAlignment(Qt::AlignCenter);
0118     ui->dependsOnLV->header()->setSectionResizeMode(PackageModel::NameCol, QHeaderView::ResizeToContents);
0119     ui->dependsOnLV->header()->setSectionResizeMode(PackageModel::VersionCol, QHeaderView::ResizeToContents);
0120     ui->dependsOnLV->header()->setSectionResizeMode(PackageModel::ArchCol, QHeaderView::Stretch);
0121     ui->dependsOnLV->header()->hideSection(PackageModel::ActionCol);
0122     ui->dependsOnLV->header()->hideSection(PackageModel::CurrentVersionCol);
0123     ui->dependsOnLV->header()->hideSection(PackageModel::OriginCol);
0124     ui->dependsOnLV->header()->hideSection(PackageModel::SizeCol);
0125 
0126     requiredByAction = menu->addAction(i18n("Required By"));
0127     requiredByAction->setCheckable(true);
0128     requiredByAction->setData(PackageKit::Transaction::RoleRequiredBy);
0129     m_actionGroup->addAction(requiredByAction);
0130     // Sets a transparent background
0131     QWidget *requiredViewport = ui->requiredByLV->viewport();
0132     QPalette requiredPalette = requiredViewport->palette();
0133     requiredPalette.setColor(requiredViewport->backgroundRole(), Qt::transparent);
0134     requiredPalette.setColor(requiredViewport->foregroundRole(), requiredPalette.color(QPalette::WindowText));
0135     requiredViewport->setPalette(requiredPalette);
0136 
0137     m_requiresModel = new PackageModel(this);
0138     m_requiresProxy = new QSortFilterProxyModel(this);
0139     m_requiresProxy->setDynamicSortFilter(true);
0140     m_requiresProxy->setSortRole(PackageModel::SortRole);
0141     m_requiresProxy->setSourceModel(m_requiresModel);
0142     ui->requiredByLV->setModel(m_requiresProxy);
0143     ui->requiredByLV->sortByColumn(0, Qt::AscendingOrder);
0144     ui->requiredByLV->header()->setDefaultAlignment(Qt::AlignCenter);
0145     ui->requiredByLV->header()->setSectionResizeMode(PackageModel::NameCol, QHeaderView::ResizeToContents);
0146     ui->requiredByLV->header()->setSectionResizeMode(PackageModel::VersionCol, QHeaderView::ResizeToContents);
0147     ui->requiredByLV->header()->setSectionResizeMode(PackageModel::ArchCol, QHeaderView::Stretch);
0148     ui->requiredByLV->header()->hideSection(PackageModel::ActionCol);
0149     ui->requiredByLV->header()->hideSection(PackageModel::CurrentVersionCol);
0150     ui->requiredByLV->header()->hideSection(PackageModel::OriginCol);
0151     ui->requiredByLV->header()->hideSection(PackageModel::SizeCol);
0152 
0153     fileListAction = menu->addAction(i18n("File List"));
0154     fileListAction->setCheckable(true);
0155     fileListAction->setData(PackageKit::Transaction::RoleGetFiles);
0156     m_actionGroup->addAction(fileListAction);
0157     // Sets a transparent background
0158     QWidget *actionsViewport = ui->filesPTE->viewport();
0159     QPalette palette = actionsViewport->palette();
0160     palette.setColor(actionsViewport->backgroundRole(), Qt::transparent);
0161     palette.setColor(actionsViewport->foregroundRole(), palette.color(QPalette::WindowText));
0162     actionsViewport->setPalette(palette);
0163 
0164     // Set the menu
0165     ui->menuTB->setMenu(menu);
0166     ui->menuTB->setIcon(QIcon::fromTheme(QLatin1String("help-about")));
0167     connect(m_actionGroup, SIGNAL(triggered(QAction*)),
0168             this, SLOT(actionActivated(QAction*)));
0169 
0170     m_busySeq = new KPixmapSequenceOverlayPainter(this);
0171     m_busySeq->setSequence(KIconLoader::global()->loadPixmapSequence(QLatin1String("process-working"), KIconLoader::SizeSmallMedium));
0172     m_busySeq->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
0173     m_busySeq->setWidget(ui->stackedWidget);
0174 
0175     // Setup the opacit effect that makes the descriptio transparent
0176     // after finished it checks in display() to see if it shouldn't show
0177     // up again. The property animation is always the same, the only different thing
0178     // is the Forward or Backward property
0179     auto effect = new QGraphicsOpacityEffect(ui->stackedWidget);
0180     effect->setOpacity(0);
0181     ui->stackedWidget->setGraphicsEffect(effect);
0182     m_fadeStacked = new QPropertyAnimation(effect, "opacity", this);
0183     m_fadeStacked->setDuration(500);
0184     m_fadeStacked->setStartValue(qreal(0));
0185     m_fadeStacked->setEndValue(qreal(1));
0186     connect(m_fadeStacked, SIGNAL(finished()), this, SLOT(display()));
0187 
0188     // It's is impossible due to some limitation in Qt to set two effects on the same
0189     // Widget
0190     m_fadeScreenshot = new QPropertyAnimation(effect, "opacity", this);
0191     auto shadow = new GraphicsOpacityDropShadowEffect(ui->screenshotL);
0192     shadow->setOpacity(0);
0193     shadow->setBlurRadius(BLUR_RADIUS);
0194     shadow->setOffset(2);
0195     shadow->setColor(QApplication::palette().dark().color());
0196     ui->screenshotL->setGraphicsEffect(shadow);
0197 
0198     m_fadeScreenshot = new QPropertyAnimation(shadow, "opacity", this);
0199     m_fadeScreenshot->setDuration(500);
0200     m_fadeScreenshot->setStartValue(qreal(0));
0201     m_fadeScreenshot->setEndValue(qreal(1));
0202     connect(m_fadeScreenshot, SIGNAL(finished()), this, SLOT(display()));
0203 
0204     // This pannel expanding
0205     auto anim1 = new QPropertyAnimation(this, "maximumSize", this);
0206     anim1->setDuration(500);
0207     anim1->setEasingCurve(QEasingCurve::OutQuart);
0208     anim1->setStartValue(QSize(QWIDGETSIZE_MAX, 0));
0209     anim1->setEndValue(QSize(QWIDGETSIZE_MAX, FINAL_HEIGHT));
0210     auto anim2 = new QPropertyAnimation(this, "minimumSize", this);
0211     anim2->setDuration(500);
0212     anim2->setEasingCurve(QEasingCurve::OutQuart);
0213     anim2->setStartValue(QSize(QWIDGETSIZE_MAX, 0));
0214     anim2->setEndValue(QSize(QWIDGETSIZE_MAX, FINAL_HEIGHT));
0215 
0216     m_expandPanel = new QParallelAnimationGroup(this);
0217     m_expandPanel->addAnimation(anim1);
0218     m_expandPanel->addAnimation(anim2);
0219     connect(m_expandPanel, SIGNAL(finished()), this, SLOT(display()));
0220 }
0221 
0222 void PackageDetails::init(PackageKit::Transaction::Roles roles)
0223 {
0224 //    qCDebug();
0225 
0226     bool setChecked = true;
0227     if (roles & PackageKit::Transaction::RoleGetDetails) {
0228         descriptionAction->setEnabled(true);
0229         descriptionAction->setChecked(setChecked);
0230         setChecked = false;
0231     } else {
0232         descriptionAction->setEnabled(false);
0233         descriptionAction->setChecked(false);
0234     }
0235 
0236     if (roles & PackageKit::Transaction::RoleDependsOn) {
0237         dependsOnAction->setEnabled(true);
0238         dependsOnAction->setChecked(setChecked);
0239         setChecked = false;
0240     } else {
0241         dependsOnAction->setEnabled(false);
0242         dependsOnAction->setChecked(false);
0243     }
0244 
0245     if (roles & PackageKit::Transaction::RoleRequiredBy) {
0246         requiredByAction->setEnabled(true);
0247         requiredByAction->setChecked(setChecked);
0248         setChecked = false;
0249     } else {
0250         requiredByAction->setEnabled(false);
0251         requiredByAction->setChecked(false);
0252     }
0253 
0254     if (roles & PackageKit::Transaction::RoleGetFiles) {
0255         fileListAction->setEnabled(true);
0256         fileListAction->setChecked(setChecked);
0257         setChecked = false;
0258     } else {
0259         fileListAction->setEnabled(false);
0260         fileListAction->setChecked(false);
0261     }
0262 
0263 }
0264 
0265 PackageDetails::~PackageDetails()
0266 {
0267     delete ui;
0268 }
0269 
0270 void PackageDetails::setPackage(const QModelIndex &index)
0271 {
0272     qCDebug(APPER) << index;
0273     QString appId = index.data(PackageModel::ApplicationId).toString();
0274     QString packageID = index.data(PackageModel::IdRole).toString();
0275 
0276     // if it's the same package and the same application, return
0277     if (packageID == m_packageID && appId == m_appId) {
0278         return;
0279     } else if (maximumSize().height() == 0) {
0280         // Expand the panel
0281         m_display = true;
0282         m_expandPanel->setDirection(QAbstractAnimation::Forward);
0283         m_expandPanel->start();
0284     } else {
0285         // Hide the old description
0286         fadeOut(PackageDetails::FadeScreenshot | PackageDetails::FadeStacked);
0287     }
0288 
0289     m_index       = index;
0290     m_appId       = appId;
0291     m_packageID   = packageID;
0292     m_hasDetails  = false;
0293     m_hasFileList = false;
0294     m_hasRequires = false;
0295     m_hasDepends  = false;
0296     qCDebug(APPER) << "appId" << appId << "m_package" << m_packageID;
0297 
0298     QString pkgIconPath = index.data(PackageModel::IconRole).toString();
0299     m_currentIcon       = PkIcons::getIcon(pkgIconPath, QString()).pixmap(64, 64);
0300     m_appName           = index.data(PackageModel::NameRole).toString();
0301 
0302     m_currentScreenshot = thumbnail(Transaction::packageName(m_packageID));
0303     qCDebug(APPER) << "current thumbnail" << m_currentScreenshot;
0304     if (!m_currentScreenshot.isEmpty()) {
0305         if (m_screenshotPath.contains(m_currentScreenshot)) {
0306             display();
0307         } else {
0308             auto tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/apper.XXXXXX.png"));
0309             tempFile->open();
0310             KIO::FileCopyJob *job = KIO::file_copy(m_currentScreenshot,
0311                                                    QUrl::fromLocalFile(tempFile->fileName()),
0312                                                    -1,
0313                                                    KIO::Overwrite | KIO::HideProgressInfo);
0314             connect(job, &KIO::FileCopyJob::result, this, &PackageDetails::resultJob);
0315         }
0316     }
0317 
0318     if (m_actionGroup->checkedAction()) {
0319         actionActivated(m_actionGroup->checkedAction());
0320     }
0321 }
0322 
0323 void PackageDetails::on_screenshotL_clicked()
0324 {
0325     const QUrl url = screenshot(Transaction::packageName(m_packageID));
0326     if (!url.isEmpty()) {
0327         auto view = new ScreenShotViewer(url);
0328         view->setWindowTitle(m_appName);
0329         view->show();
0330     }
0331 }
0332 
0333 void PackageDetails::hidePackageVersion(bool hide)
0334 {
0335     m_hideVersion = hide;
0336 }
0337 
0338 void PackageDetails::hidePackageArch(bool hide)
0339 {
0340     m_hideArch = hide;
0341 }
0342 
0343 void PackageDetails::actionActivated(QAction *action)
0344 {
0345     // don't fade the screenshot
0346     // if the package changed setPackage() fades both
0347     fadeOut(FadeStacked);
0348     qCDebug(APPER);
0349 
0350     // disconnect the transaction
0351     // so that we don't get old data
0352     if (m_transaction) {
0353         disconnect(m_transaction, SIGNAL(details(PackageKit::Details)),
0354                    this, SLOT(description(PackageKit::Details)));
0355         disconnect(m_transaction, SIGNAL(package(PackageKit::Transaction::Info,QString,QString)),
0356                    m_dependsModel, SLOT(addPackage(PackageKit::Transaction::Info,QString,QString)));
0357         disconnect(m_transaction, SIGNAL(package(PackageKit::Transaction::Info,QString,QString)),
0358                    m_requiresModel, SLOT(addPackage(PackageKit::Transaction::Info,QString,QString)));
0359         disconnect(m_transaction, SIGNAL(files(QString,QStringList)),
0360                    this, SLOT(files(QString,QStringList)));
0361         disconnect(m_transaction, SIGNAL(finished(PackageKit::Transaction::Exit,uint)),
0362                    this, SLOT(finished()));
0363         m_transaction = nullptr;
0364     }
0365 
0366     // Check to see if we don't already have the required data
0367     uint role = action->data().toUInt();
0368     switch (role) {
0369     case PackageKit::Transaction::RoleGetDetails:
0370         if (m_hasDetails) {
0371             description(m_details);
0372             display();
0373             return;
0374         }
0375         break;
0376     case PackageKit::Transaction::RoleDependsOn:
0377         if (m_hasDepends) {
0378             display();
0379             return;
0380         }
0381         break;
0382     case PackageKit::Transaction::RoleRequiredBy:
0383         if (m_hasRequires) {
0384             display();
0385             return;
0386         }
0387         break;
0388     case PackageKit::Transaction::RoleGetFiles:
0389         if (m_hasFileList) {
0390             display();
0391             return;
0392         }
0393         break;
0394     }
0395 
0396     // we don't have the data
0397     qCDebug(APPER) << "New transaction";
0398     switch (role) {
0399     case PackageKit::Transaction::RoleGetDetails:
0400         m_transaction = Daemon::getDetails(m_packageID);
0401         connect(m_transaction, SIGNAL(details(PackageKit::Details)),
0402                 SLOT(description(PackageKit::Details)));
0403         break;
0404     case PackageKit::Transaction::RoleDependsOn:
0405         m_dependsModel->clear();
0406         m_transaction = Daemon::dependsOn(m_packageID, PackageKit::Transaction::FilterNone, false);
0407         connect(m_transaction, SIGNAL(package(PackageKit::Transaction::Info,QString,QString)),
0408                 m_dependsModel, SLOT(addPackage(PackageKit::Transaction::Info,QString,QString)));
0409         connect(m_transaction, SIGNAL(finished(PackageKit::Transaction::Exit,uint)),
0410                 m_dependsModel, SLOT(finished()));
0411         break;
0412     case PackageKit::Transaction::RoleRequiredBy:
0413         m_requiresModel->clear();
0414         m_transaction = Daemon::requiredBy(m_packageID, PackageKit::Transaction::FilterNone, false);
0415         connect(m_transaction, SIGNAL(package(PackageKit::Transaction::Info,QString,QString)),
0416                 m_requiresModel, SLOT(addPackage(PackageKit::Transaction::Info,QString,QString)));
0417         connect(m_transaction, SIGNAL(finished(PackageKit::Transaction::Exit,uint)),
0418                 m_requiresModel, SLOT(finished()));
0419         break;
0420     case PackageKit::Transaction::RoleGetFiles:
0421         m_currentFileList.clear();
0422         m_transaction = Daemon::getFiles(m_packageID);
0423         connect(m_transaction, SIGNAL(files(QString,QStringList)),
0424                 this, SLOT(files(QString,QStringList)));
0425         break;
0426     default:
0427         qWarning() << Q_FUNC_INFO << "Oops, unhandled role, please report" << role;
0428         return;
0429     }
0430     connect(m_transaction, SIGNAL(finished(PackageKit::Transaction::Exit,uint)),
0431             this, SLOT(finished()));
0432     qCDebug(APPER) <<"transaction running";
0433 
0434     m_busySeq->start();
0435 }
0436 
0437 void PackageDetails::resultJob(KJob *job)
0438 {
0439     KIO::FileCopyJob *fJob = qobject_cast<KIO::FileCopyJob*>(job);
0440     if (!fJob->error()) {
0441         m_screenshotPath[fJob->srcUrl()] = fJob->destUrl().toLocalFile();
0442         display();
0443     }
0444 }
0445 
0446 void PackageDetails::hide()
0447 {
0448     m_display = false;
0449     // Clean the old description otherwise if the user selects the same
0450     // package the pannel won't expand
0451     m_packageID.clear();
0452     m_appId.clear();
0453 
0454     if (maximumSize().height() == FINAL_HEIGHT) {
0455         if (m_fadeStacked->currentValue().toReal() == 0 &&
0456             m_fadeScreenshot->currentValue().toReal() == 0) {
0457             // Screen shot and description faded let's shrink the pannel
0458             m_expandPanel->setDirection(QAbstractAnimation::Backward);
0459             m_expandPanel->start();
0460         } else {
0461             // Hide current description
0462             fadeOut(PackageDetails::FadeScreenshot | PackageDetails::FadeStacked);
0463         }
0464     }
0465 }
0466 
0467 void PackageDetails::fadeOut(FadeWidgets widgets)
0468 {
0469     // Fade out only if needed
0470     if ((widgets & FadeStacked) && m_fadeStacked->currentValue().toReal() != 0) {
0471         m_fadeStacked->setDirection(QAbstractAnimation::Backward);
0472         m_fadeStacked->start();
0473     }
0474 
0475     // Fade out the screenshot only if needed
0476     if ((widgets & FadeScreenshot) && m_fadeScreenshot->currentValue().toReal() != 0) {
0477         ui->screenshotL->unsetCursor();
0478         m_fadeScreenshot->setDirection(QAbstractAnimation::Backward);
0479         m_fadeScreenshot->start();
0480     }
0481 }
0482 
0483 void PackageDetails::display()
0484 {
0485     // If we shouldn't be showing hide the pannel
0486     if (!m_display) {
0487         hide();
0488     } else if (maximumSize().height() == FINAL_HEIGHT) {
0489         emit ensureVisible(m_index);
0490 
0491         // Check to see if the stacked widget is transparent
0492         if (m_fadeStacked->currentValue().toReal() == 0 &&
0493             m_actionGroup->checkedAction())
0494         {
0495             bool fadeIn = false;
0496             switch (m_actionGroup->checkedAction()->data().toUInt()) {
0497             case PackageKit::Transaction::RoleGetDetails:
0498                 if (m_hasDetails) {
0499                     setupDescription();
0500                     fadeIn = true;
0501                 }
0502                 break;
0503             case PackageKit::Transaction::RoleDependsOn:
0504                 if (m_hasDepends) {
0505                     if (ui->stackedWidget->currentWidget() != ui->pageDepends) {
0506                         ui->stackedWidget->setCurrentWidget(ui->pageDepends);
0507                     }
0508                     fadeIn = true;
0509                 }
0510                 break;
0511             case PackageKit::Transaction::RoleRequiredBy:
0512                 if (m_hasRequires) {
0513                     if (ui->stackedWidget->currentWidget() != ui->pageRequired) {
0514                         ui->stackedWidget->setCurrentWidget(ui->pageRequired);
0515                     }
0516                     fadeIn = true;
0517                 }
0518                 break;
0519             case PackageKit::Transaction::RoleGetFiles:
0520                 if (m_hasFileList) {
0521                     ui->filesPTE->clear();
0522                     if (m_currentFileList.isEmpty()) {
0523                         ui->filesPTE->insertPlainText(i18n("No files were found."));
0524                     } else {
0525                         m_currentFileList.sort();
0526                         ui->filesPTE->insertPlainText(m_currentFileList.join(QLatin1Char('\n')));
0527                     }
0528 
0529                     if (ui->stackedWidget->currentWidget() != ui->pageFiles) {
0530                         ui->stackedWidget->setCurrentWidget(ui->pageFiles);
0531                     }
0532                     ui->filesPTE->verticalScrollBar()->setValue(0);
0533                     fadeIn = true;
0534                 }
0535                 break;
0536             }
0537 
0538             if (fadeIn) {
0539                 // Fade In
0540                 m_fadeStacked->setDirection(QAbstractAnimation::Forward);
0541                 m_fadeStacked->start();
0542             }
0543         }
0544 
0545         // Check to see if we have a screen shot and if we are
0546         // transparent, and make sure the details are going
0547         // to be shown
0548         if (m_fadeScreenshot->currentValue().toReal() == 0 &&
0549             m_screenshotPath.contains(m_currentScreenshot) &&
0550             m_fadeStacked->direction() == QAbstractAnimation::Forward) {
0551             QPixmap pixmap;
0552             pixmap = QPixmap(m_screenshotPath[m_currentScreenshot])
0553                              .scaled(160,120, Qt::KeepAspectRatio, Qt::SmoothTransformation);
0554             ui->screenshotL->setPixmap(pixmap);
0555             ui->screenshotL->setCursor(Qt::PointingHandCursor);
0556             // Fade In
0557             m_fadeScreenshot->setDirection(QAbstractAnimation::Forward);
0558             m_fadeScreenshot->start();
0559         }
0560     }
0561 }
0562 
0563 void PackageDetails::setupDescription()
0564 {
0565     if (ui->stackedWidget->currentWidget() != ui->pageDescription) {
0566         ui->stackedWidget->setCurrentWidget(ui->pageDescription);
0567     }
0568 
0569     if (!m_hasDetails) {
0570         // Oops we don't have any details
0571         ui->descriptionL->setText(i18n("Could not fetch software details"));
0572         ui->descriptionL->show();
0573 
0574         // Hide stuff so we don't display outdated data
0575         ui->homepageL->hide();
0576         ui->pathL->hide();
0577         ui->licenseL->hide();
0578         ui->sizeL->hide();
0579         ui->iconL->clear();
0580     }
0581 
0582     if (!m_detailsDescription.isEmpty()) {
0583         ui->descriptionL->setText(m_detailsDescription.replace(QLatin1Char('\n'), QLatin1String("<br>")));
0584         ui->descriptionL->show();
0585     } else {
0586         ui->descriptionL->clear();
0587     }
0588 
0589     if (!m_details.url().isEmpty()) {
0590         ui->homepageL->setText(QLatin1String("<a href=\"") + m_details.url() + QLatin1String("\">") +
0591                                m_details.url() + QLatin1String("</a>"));
0592         ui->homepageL->show();
0593     } else {
0594         ui->homepageL->hide();
0595     }
0596 
0597     // Let's try to find the application's path in human user
0598     // readable easiest form :D
0599     KService::Ptr service = KService::serviceByDesktopName(m_appId);
0600     QVector<QPair<QString, QString> > ret;
0601     if (service) {
0602         ret = locateApplication(QString(), service->menuId());
0603     }
0604     if (ret.isEmpty()) {
0605         ui->pathL->hide();
0606     } else {
0607         QString path;
0608         path.append(QString(QLatin1String("<img width=\"16\" heigh=\"16\"src=\"%1\"/>"))
0609                     .arg(KIconLoader::global()->iconPath(QLatin1String("kde"), KIconLoader::Small)));
0610         path.append(QString(QLatin1String("&nbsp;%1 <img width=\"16\" heigh=\"16\" src=\"%2\"/>&nbsp;%3"))
0611                     .arg(QString::fromUtf8("➜"))
0612                     .arg(KIconLoader::global()->iconPath(QLatin1String("applications-other"), KIconLoader::Small))
0613                     .arg(i18n("Applications")));
0614         for (int i = 0; i < ret.size(); i++) {
0615             path.append(QString(QLatin1String("&nbsp;%1&nbsp;<img width=\"16\" heigh=\"16\" src=\"%2\"/>&nbsp;%3"))
0616                         .arg(QString::fromUtf8("➜"))
0617                         .arg(KIconLoader::global()->iconPath(ret.at(i).second, KIconLoader::Small))
0618                         .arg(ret.at(i).first));
0619         }
0620         ui->pathL->setText(path);
0621         ui->pathL->show();
0622     }
0623 
0624 //     if (details->group() != Package::UnknownGroup) {
0625 // //         description += "<tr><td align=\"right\"><b>" + i18nc("Group of the package", "Group") + ":</b></td><td>"
0626 // //                     + PkStrings::groups(details->group())
0627 // //                     + "</td></tr>";
0628 //     }
0629 
0630     if (!m_details.license().isEmpty() && m_details.license() != QLatin1String("unknown")) {
0631         // We have a license, check if we have and should show show package version
0632         if (!m_hideVersion && !Transaction::packageVersion(m_details.packageId()).isEmpty()) {
0633             ui->licenseL->setText(Transaction::packageVersion(m_details.packageId()) + QLatin1String(" - ") + m_details.license());
0634         } else {
0635             ui->licenseL->setText(m_details.license());
0636         }
0637         ui->licenseL->show();
0638     } else if (!m_hideVersion) {
0639         ui->licenseL->setText(Transaction::packageVersion(m_details.packageId()));
0640         ui->licenseL->show();
0641     } else {
0642         ui->licenseL->hide();
0643     }
0644 
0645     if (m_details.size() > 0) {
0646         QString size = KFormat().formatByteSize(m_details.size());
0647         if (!m_hideArch && !Transaction::packageArch(m_details.packageId()).isEmpty()) {
0648             ui->sizeL->setText(size % QLatin1String(" (") % Transaction::packageArch(m_details.packageId()) % QLatin1Char(')'));
0649         } else {
0650             ui->sizeL->setText(size);
0651         }
0652         ui->sizeL->show();
0653     } else if (!m_hideArch && !Transaction::packageArch(m_details.packageId()).isEmpty()) {
0654         ui->sizeL->setText(Transaction::packageArch(m_details.packageId()));
0655     } else {
0656         ui->sizeL->hide();
0657     }
0658 
0659     if (m_currentIcon.isNull()) {
0660         ui->iconL->clear();
0661     } else {
0662         ui->iconL->setPixmap(m_currentIcon);
0663     }
0664 }
0665 
0666 QVector<QPair<QString, QString> > PackageDetails::locateApplication(const QString &_relPath, const QString &menuId) const
0667 {
0668     QVector<QPair<QString, QString> > ret;
0669     KServiceGroup::Ptr root = KServiceGroup::group(_relPath);
0670 
0671     if (!root || !root->isValid()) {
0672         return ret;
0673     }
0674 
0675     KServiceGroup::List list = root->entries(false /* sorted */,
0676                                                    true /* exclude no display entries */,
0677                                                    false /* allow separators */);
0678 
0679     //! TODO: Port to KF5 properly
0680     Q_UNUSED(menuId)
0681 #if 0
0682     for (KServiceGroup::List::ConstIterator it = list.begin(); it != list.end(); it++) {
0683         KSycocaEntry::Ptr = (*it);
0684 
0685         if (p->isType(KST_KService)) {
0686             KService *service = static_cast<KService *>(p.get());
0687 
0688             if (service->noDisplay()) {
0689                 continue;
0690             }
0691 
0692 //             qCDebug(APPER) << menuId << service->menuId();
0693             if (service->menuId() == menuId) {
0694                 QPair<QString, QString> pair;
0695                 pair.first  = service->name();
0696                 pair.second = service->icon();
0697                 ret << pair;
0698 //                 qCDebug(APPER) << "FOUND!";
0699                 return ret;
0700             }
0701         } else if (p->isType(KST_KServiceGroup)) {
0702             KServiceGroup *serviceGroup = static_cast<KServiceGroup *>(p.get());
0703 
0704             if (serviceGroup->noDisplay() || serviceGroup->childCount() == 0) {
0705                 continue;
0706             }
0707 
0708             QVector<QPair<QString, QString> > found;
0709             found = locateApplication(serviceGroup->relPath(), menuId);
0710             if (!found.isEmpty()) {
0711                 QPair<QString, QString> pair;
0712                 pair.first  = serviceGroup->caption();
0713                 pair.second = serviceGroup->icon();
0714                 ret << pair;
0715                 ret << found;
0716                 return ret;
0717             }
0718         } else {
0719             kWarning(250) << "KServiceGroup: Unexpected object in list!";
0720             continue;
0721         }
0722     }
0723 #endif
0724 
0725     return ret;
0726 }
0727 
0728 QUrl PackageDetails::thumbnail(const QString &pkgName) const
0729 {
0730 #ifndef HAVE_APPSTREAM
0731     Q_UNUSED(pkgName)
0732     return QUrl();
0733 #else
0734     return AppStreamHelper::instance()->thumbnail(pkgName);
0735 #endif
0736 }
0737 
0738 QUrl PackageDetails::screenshot(const QString &pkgName) const
0739 {
0740 #ifndef HAVE_APPSTREAM
0741     Q_UNUSED(pkgName)
0742     return QUrl();
0743 #else
0744     return AppStreamHelper::instance()->screenshot(pkgName);
0745 #endif
0746 }
0747 
0748 void PackageDetails::description(const PackageKit::Details &details)
0749 {
0750     qCDebug(APPER) << details;
0751     m_details = details;
0752     m_detailsDescription = details.description();
0753     m_hasDetails = true;
0754 
0755 #ifdef HAVE_APPSTREAM
0756     // check if we have application details from Appstream data
0757     // FIXME: The whole AppStream handling sucks badly, since it was added later
0758     // and on to of the package-based model. So we can't respect the "multiple apps
0759     // in one package" case here.
0760     const QList<AppStream::Component> apps = AppStreamHelper::instance()->applications(Transaction::packageName(m_packageID));
0761     for (const AppStream::Component &app : apps) {
0762         if (!app.description().isEmpty()) {
0763             m_detailsDescription = app.description();
0764             break;
0765         }
0766     }
0767 #endif
0768 }
0769 
0770 void PackageDetails::finished()
0771 {
0772     if (m_busySeq) {
0773         m_busySeq->stop();
0774     }
0775     m_transaction = nullptr;
0776 
0777     auto transaction = qobject_cast<PackageKit::Transaction*>(sender());
0778     qCDebug(APPER);
0779     if (transaction) {
0780         qCDebug(APPER) << transaction->role() << PackageKit::Transaction::RoleGetDetails;
0781         if (transaction->role() == PackageKit::Transaction::RoleGetDetails) {
0782             m_hasDetails = true;
0783         } else if (transaction->role() == PackageKit::Transaction::RoleGetFiles) {
0784             m_hasFileList = true;
0785         } else if (transaction->role() == PackageKit::Transaction::RoleRequiredBy) {
0786             m_hasRequires = true;
0787         } else if (transaction->role() == PackageKit::Transaction::RoleDependsOn) {
0788             m_hasDepends  = true;
0789         } else {
0790             return;
0791         }
0792 
0793         display();
0794     }
0795 }
0796 
0797 void PackageDetails::files(const QString &packageID, const QStringList &files)
0798 {
0799     Q_UNUSED(packageID)
0800     m_currentFileList = files;
0801 }
0802 
0803 #include "moc_PackageDetails.cpp"