File indexing completed on 2024-03-24 17:21:58

0001 /***************************************************************************
0002  *   Copyright (C) 2008-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 "ApperKCM.h"
0022 #include "ui_ApperKCM.h"
0023 
0024 #include <config.h>
0025 
0026 #include <KAboutData>
0027 
0028 #include <KLocalizedString>
0029 #include <QStandardPaths>
0030 #include <KMessageBox>
0031 #include <KFileItemDelegate>
0032 #include <KHelpMenu>
0033 #include <QTabBar>
0034 #include <QToolBar>
0035 #include <QSignalMapper>
0036 #include <QTimer>
0037 #include <QPointer>
0038 #include <QKeyEvent>
0039 
0040 #include <PackageModel.h>
0041 #include <ApplicationSortFilterModel.h>
0042 #include <ChangesDelegate.h>
0043 #include <PkStrings.h>
0044 #include <PkIcons.h>
0045 #include <PkTransactionWidget.h>
0046 
0047 #ifdef HAVE_APPSTREAM
0048 #include <AppStream.h>
0049 #endif
0050 
0051 #include <QLoggingCategory>
0052 #include <Daemon>
0053 
0054 #include "FiltersMenu.h"
0055 #include "BrowseView.h"
0056 #include "CategoryModel.h"
0057 #include "TransactionHistory.h"
0058 #include "Settings/Settings.h"
0059 #include "Updater/Updater.h"
0060 
0061 Q_LOGGING_CATEGORY(APPER, "apper")
0062 
0063 #define BAR_SEARCH   0
0064 #define BAR_UPDATE   1
0065 #define BAR_SETTINGS 2
0066 #define BAR_TITLE    3
0067 
0068 ApperKCM::ApperKCM(QWidget *parent) :
0069     QWidget(parent),
0070     ui(new Ui::ApperKCM),
0071     m_findIcon(QIcon::fromTheme(QLatin1String("edit-find"))),
0072     m_cancelIcon(QIcon::fromTheme(QLatin1String("dialog-cancel")))
0073 {
0074     ui->setupUi(this);
0075 
0076     // store the actions supported by the backend
0077     connect(Daemon::global(), &Daemon::changed, this, &ApperKCM::daemonChanged);
0078 
0079     // Set the current locale
0080     Daemon::global()->setHints(QLatin1String("locale=") + QLocale::system().name() + QLatin1String(".UTF-8"));
0081 
0082     // Browse TAB
0083     ui->backTB->setIcon(QIcon::fromTheme(QLatin1String("go-previous")));
0084 
0085     // create our toolbar
0086     auto toolBar = new QToolBar(this);
0087     ui->gridLayout_2->addWidget(toolBar);
0088     toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
0089 
0090     connect(ui->browseView, &BrowseView::categoryActivated, this, &ApperKCM::on_homeView_activated);
0091 
0092     auto findMenu = new QMenu(this);
0093     // find is just a generic name in case we don't have any search method
0094     m_genericActionK = new KToolBarPopupAction(m_findIcon, i18n("Find"), this);
0095     toolBar->addAction(m_genericActionK);
0096 
0097     // Add actions that the backend supports
0098     findMenu->addAction(ui->actionFindName);
0099     setCurrentAction(ui->actionFindName);
0100 
0101     findMenu->addAction(ui->actionFindDescription);
0102     if (!m_currentAction) {
0103         setCurrentAction(ui->actionFindDescription);
0104     }
0105 
0106     findMenu->addAction(ui->actionFindFile);
0107     if (!m_currentAction) {
0108         setCurrentAction(ui->actionFindFile);
0109     }
0110 
0111 
0112     // If no action was set we can't use this search
0113     if (m_currentAction == nullptr) {
0114         m_genericActionK->setEnabled(false);
0115         ui->searchKLE->setEnabled(false);
0116     } else {
0117         // Check to see if we need the KToolBarPopupAction
0118         setCurrentActionCancel(false);
0119         if (findMenu->actions().size() > 1) {
0120             m_currentAction->setVisible(false);
0121             m_genericActionK->setMenu(findMenu);
0122         } else {
0123             m_currentAction->setVisible(true);
0124             toolBar->removeAction(m_genericActionK);
0125             toolBar->addAction(m_currentAction);
0126         }
0127         connect(m_genericActionK, &KToolBarPopupAction::triggered, this, &ApperKCM::genericActionKTriggered);
0128     }
0129 
0130     // Create the groups model
0131     m_groupsModel = new CategoryModel(this);
0132     ui->browseView->setCategoryModel(m_groupsModel);
0133     connect(m_groupsModel, &CategoryModel::finished, this, &ApperKCM::setupHomeModel);
0134     ui->homeView->setSpacing(10);
0135     ui->homeView->viewport()->setAttribute(Qt::WA_Hover);
0136 
0137     KFileItemDelegate *delegate = new KFileItemDelegate(this);
0138     delegate->setWrapMode(QTextOption::WordWrap);
0139     ui->homeView->setItemDelegate(delegate);
0140 
0141     // install the backend filters
0142     ui->filtersTB->setMenu(m_filtersMenu = new FiltersMenu(this));
0143     connect(m_filtersMenu, &FiltersMenu::filtersChanged, this, &ApperKCM::search);
0144     ui->filtersTB->setIcon(QIcon::fromTheme(QLatin1String("view-filter")));
0145     ApplicationSortFilterModel *proxy = ui->browseView->proxy();
0146     proxy->setApplicationFilter(m_filtersMenu->filterApplications());
0147     connect(m_filtersMenu, QOverload<bool>::of(&FiltersMenu::filterApplications), proxy, &ApplicationSortFilterModel::setApplicationFilter);
0148 
0149     //initialize the model, delegate, client and  connect it's signals
0150     m_browseModel = ui->browseView->model();
0151 
0152     // CHANGES TAB
0153     ui->changesView->viewport()->setAttribute(Qt::WA_Hover);
0154     m_changesModel = new PackageModel(this);
0155     auto changedProxy = new KCategorizedSortFilterProxyModel(this);
0156     changedProxy->setSourceModel(m_changesModel);
0157     changedProxy->setDynamicSortFilter(true);
0158     changedProxy->setCategorizedModel(true);
0159     changedProxy->setSortCaseSensitivity(Qt::CaseInsensitive);
0160     changedProxy->setSortRole(PackageModel::SortRole);
0161     changedProxy->sort(0);
0162     ui->changesView->setModel(changedProxy);
0163     auto changesDelegate = new ChangesDelegate(ui->changesView);
0164     changesDelegate->setExtendPixmapWidth(0);
0165     ui->changesView->setItemDelegate(changesDelegate);
0166 
0167     // Connect this signal to keep track of changes
0168     connect(m_browseModel, &PackageModel::changed, this, &ApperKCM::checkChanged);
0169 
0170     // packageUnchecked from changes model
0171     connect(m_changesModel, &PackageModel::packageUnchecked, m_changesModel, &PackageModel::removePackage);
0172     connect(m_changesModel, &PackageModel::packageUnchecked, m_browseModel, &PackageModel::uncheckPackageDefault);
0173 
0174     ui->reviewMessage->setIcon(QIcon::fromTheme(QLatin1String("edit-redo")));
0175     ui->reviewMessage->setText(i18n("Some software changes were made"));
0176     auto reviewAction = new QAction(i18n("Review"), this);
0177     connect(reviewAction, &QAction::triggered, this, &ApperKCM::showReviewPages);
0178     ui->reviewMessage->addAction(reviewAction);
0179     auto discardAction = new QAction(i18n("Discard"), this);
0180     connect(discardAction, &QAction::triggered, m_browseModel, &PackageModel::uncheckAll);
0181     ui->reviewMessage->addAction(discardAction);
0182     auto applyAction = new QAction(i18n("Apply"), this);
0183     connect(applyAction, &QAction::triggered, this, &ApperKCM::save);
0184     ui->reviewMessage->addAction(applyAction);
0185     ui->reviewMessage->setCloseButtonVisible(false);
0186     ui->reviewMessage->hide();
0187     connect(ui->reviewMessage, &KMessageWidget::showAnimationFinished, this, [this] () {
0188         if (!ui->reviewMessage->property("HasChanges").toBool()) {
0189             ui->reviewMessage->animatedHide();
0190         }
0191     });
0192     connect(ui->reviewMessage, &KMessageWidget::hideAnimationFinished, this, [this] () {
0193         if (ui->reviewMessage->property("HasChanges").toBool()) {
0194             ui->reviewMessage->animatedShow();
0195         }
0196     });
0197 
0198     auto menu = new QMenu(this);
0199     ui->settingsTB->setMenu(menu);
0200     ui->settingsTB->setIcon(QIcon::fromTheme(QLatin1String("preferences-other")));
0201 
0202     auto signalMapper = new QSignalMapper(this);
0203     connect(signalMapper, QOverload<const QString &>::of(&QSignalMapper::mapped), this, &ApperKCM::setPage);
0204 
0205     QAction *action;
0206     action = menu->addAction(QIcon::fromTheme(QLatin1String("view-history")), i18n("History"));
0207     signalMapper->setMapping(action, QLatin1String("history"));
0208     connect(action, &QAction::triggered, signalMapper, QOverload<>::of(&QSignalMapper::map));
0209 
0210     action = menu->addAction(QIcon::fromTheme(QLatin1String("preferences-other")), i18n("Settings"));
0211     signalMapper->setMapping(action, QLatin1String("settings"));
0212     connect(action, &QAction::triggered, signalMapper, QOverload<>::of(&QSignalMapper::map));
0213 
0214     auto helpMenu = new KHelpMenu(this, KAboutData::applicationData());
0215     menu->addMenu(helpMenu->menu());
0216 
0217     // Make sure the search bar is visible
0218     ui->stackedWidgetBar->setCurrentIndex(BAR_SEARCH);
0219 }
0220 
0221 void ApperKCM::setupHomeModel()
0222 {
0223     KCategorizedSortFilterProxyModel *oldProxy = m_groupsProxyModel;
0224     m_groupsProxyModel = new KCategorizedSortFilterProxyModel(this);
0225     m_groupsProxyModel->setSourceModel(m_groupsModel);
0226     m_groupsProxyModel->setCategorizedModel(true);
0227     m_groupsProxyModel->sort(0);
0228     ui->homeView->setModel(m_groupsProxyModel);
0229     if (oldProxy) {
0230         oldProxy->deleteLater();
0231     }
0232 }
0233 
0234 void ApperKCM::genericActionKTriggered()
0235 {
0236     m_currentAction->trigger();
0237 }
0238 
0239 void ApperKCM::setCurrentAction(QAction *action)
0240 {
0241     // just load the new action if it changes this
0242     // also ensures that our menu has more than one action
0243     if (m_currentAction != action) {
0244         // hides the item from the list
0245         action->setVisible(false);
0246         // ensures the current action was created
0247         if (m_currentAction) {
0248             // show the item back in the list
0249             m_currentAction->setVisible(true);
0250         }
0251         m_currentAction = action;
0252         // copy data from the curront action
0253         m_genericActionK->setText(m_currentAction->text());
0254         m_genericActionK->setIcon(m_currentAction->icon());
0255     }
0256 }
0257 
0258 void ApperKCM::setCurrentActionEnabled(bool state)
0259 {
0260     if (m_currentAction) {
0261         m_currentAction->setEnabled(state);
0262     }
0263     m_genericActionK->setEnabled(state);
0264 }
0265 
0266 void ApperKCM::setCurrentActionCancel(bool cancel)
0267 {
0268     if (cancel) {
0269         // every action should like cancel
0270         ui->actionFindName->setText(i18n("&Cancel"));
0271         ui->actionFindFile->setText(i18n("&Cancel"));
0272         ui->actionFindDescription->setText(i18n("&Cancel"));
0273         m_genericActionK->setText(i18n("&Cancel"));
0274         // set cancel icons
0275         ui->actionFindFile->setIcon(m_cancelIcon);
0276         ui->actionFindDescription->setIcon(m_cancelIcon);
0277         ui->actionFindName->setIcon(m_cancelIcon);
0278         m_genericActionK->setIcon(m_cancelIcon);
0279     } else {
0280         ui->actionFindName->setText(i18n("Find by &name"));
0281         ui->actionFindFile->setText(i18n("Find by f&ile name"));
0282         ui->actionFindDescription->setText(i18n("Find by &description"));
0283         // Define actions icon
0284         ui->actionFindFile->setIcon(QIcon::fromTheme(QLatin1String("document-open")));
0285         ui->actionFindDescription->setIcon(QIcon::fromTheme(QLatin1String("document-edit")));
0286         ui->actionFindName->setIcon(m_findIcon);
0287         m_genericActionK->setIcon(m_findIcon);
0288         if (m_currentAction) {
0289             m_genericActionK->setText(m_currentAction->text());
0290         } else {
0291             // This might happen when the backend can
0292             // only search groups
0293             m_genericActionK->setText(i18n("Find"));
0294         }
0295     }
0296 }
0297 
0298 void ApperKCM::checkChanged()
0299 {
0300     bool hasChanges = false;
0301     if (ui->stackedWidget->currentWidget() == ui->pageHome ||
0302             ui->stackedWidget->currentWidget() == ui->pageChanges ||
0303             ui->stackedWidget->currentWidget() == ui->pageBrowse) {
0304         hasChanges = m_browseModel->hasChanges();
0305         if (!hasChanges && ui->stackedWidget->currentWidget() == ui->pageChanges) {
0306             search();
0307         }
0308         if (hasChanges) {
0309             if (!ui->reviewMessage->isHideAnimationRunning() && !ui->reviewMessage->isShowAnimationRunning()) {
0310                 ui->reviewMessage->animatedShow();
0311             }
0312         } else {
0313             if (!ui->reviewMessage->isHideAnimationRunning() && !ui->reviewMessage->isShowAnimationRunning()) {
0314                 ui->reviewMessage->animatedHide();
0315             }
0316         }
0317         ui->reviewMessage->setProperty("HasChanges", hasChanges);
0318     } else if (ui->stackedWidget->currentWidget() == m_updaterPage) {
0319         hasChanges = m_updaterPage->hasChanges();
0320     } else if (ui->stackedWidget->currentWidget() == m_settingsPage) {
0321         hasChanges = m_settingsPage->hasChanges();
0322     }
0323 
0324     emit changed(hasChanges);
0325 }
0326 
0327 void ApperKCM::errorCode(PackageKit::Transaction::Error error, const QString &details)
0328 {
0329     if (error != Transaction::ErrorTransactionCancelled) {
0330         KMessageBox::detailedError(this, PkStrings::errorMessage(error), details, PkStrings::error(error), KMessageBox::Notify);
0331     }
0332 }
0333 
0334 ApperKCM::~ApperKCM()
0335 {
0336     delete ui;
0337 }
0338 
0339 void ApperKCM::daemonChanged()
0340 {
0341     Transaction::Roles roles = Daemon::roles();
0342     if (m_roles == roles) {
0343         return;
0344     }
0345     m_roles = roles;
0346 
0347     // Add actions that the backend supports
0348     ui->actionFindName->setEnabled(roles & Transaction::RoleSearchName);
0349     ui->actionFindDescription->setEnabled(roles & Transaction::RoleSearchDetails);
0350     ui->actionFindFile->setEnabled(roles & Transaction::RoleSearchFile);
0351 
0352     ui->browseView->init(roles);
0353 
0354     m_groupsModel->setRoles(roles);
0355 
0356     m_filtersMenu->setFilters(Daemon::filters());
0357 }
0358 
0359 void ApperKCM::on_actionFindName_triggered()
0360 {
0361     setCurrentAction(ui->actionFindName);
0362     if (!ui->searchKLE->text().isEmpty()) {
0363         // cache the search
0364         m_searchRole   = Transaction::RoleSearchName;
0365         m_searchString = ui->searchKLE->text();
0366         // create the main transaction
0367         search();
0368     }
0369 }
0370 
0371 void ApperKCM::on_actionFindDescription_triggered()
0372 {
0373     setCurrentAction(ui->actionFindDescription);
0374     if (!ui->searchKLE->text().isEmpty()) {
0375         // cache the search
0376         m_searchRole   = Transaction::RoleSearchDetails;
0377         m_searchString = ui->searchKLE->text();
0378         // create the main transaction
0379         search();
0380     }
0381 }
0382 
0383 void ApperKCM::on_actionFindFile_triggered()
0384 {
0385     setCurrentAction(ui->actionFindFile);
0386     if (!ui->searchKLE->text().isEmpty()) {
0387         // cache the search
0388         m_searchRole    = Transaction::RoleSearchFile;
0389         m_searchString  = ui->searchKLE->text();
0390         // create the main transaction
0391         search();
0392     }
0393 }
0394 
0395 void ApperKCM::on_homeView_activated(const QModelIndex &index)
0396 {
0397     if (index.isValid()) {
0398         const auto proxy = qobject_cast<const QSortFilterProxyModel*>(index.model());
0399         // If the cast failed it's the index came from browseView
0400         if (proxy) {
0401             m_searchParentCategory = proxy->mapToSource(index);
0402         } else {
0403             m_searchParentCategory = index;
0404         }
0405 
0406         // cache the search
0407         m_searchRole = static_cast<Transaction::Role>(index.data(CategoryModel::SearchRole).toUInt());
0408         qCDebug(APPER) << m_searchRole << index.data(CategoryModel::CategoryRole).toString();
0409         if (m_searchRole == Transaction::RoleResolve) {
0410 #ifdef HAVE_APPSTREAM
0411             CategoryMatcher parser = index.data(CategoryModel::CategoryRole).value<CategoryMatcher>();
0412 //            m_searchCategory = AppStream::instance()->findPkgNames(parser);
0413 #endif // HAVE_APPSTREAM
0414         } else if (m_searchRole == Transaction::RoleSearchGroup) {
0415             if (index.data(CategoryModel::GroupRole).type() == QVariant::String) {
0416                 QString category = index.data(CategoryModel::GroupRole).toString();
0417                 if (category.startsWith(QLatin1Char('@')) ||
0418                     (category.startsWith(QLatin1String("repo:")) && category.size() > 5)) {
0419                     m_searchGroupCategory = category;
0420                 } else {
0421                     m_groupsModel->setRootIndex(m_searchParentCategory);
0422                     ui->backTB->setEnabled(true);
0423                     return;
0424                 }
0425             } else {
0426                 m_searchGroupCategory.clear();
0427                 int groupRole = index.data(CategoryModel::GroupRole).toInt();
0428                 m_searchGroup = static_cast<PackageKit::Transaction::Group>(groupRole);
0429                 m_searchString = index.data().toString(); // Store the nice name to change the title
0430             }
0431         } else if (m_searchRole == Transaction::RoleGetUpdates) {
0432             setPage(QLatin1String("updates"));
0433             return;
0434         }
0435 
0436         // create the main transaction
0437         search();
0438     }
0439 }
0440 
0441 bool ApperKCM::canChangePage()
0442 {
0443     bool changed;
0444     // Check if we can change the current page
0445     if (ui->stackedWidget->currentWidget() == m_updaterPage) {
0446         changed = m_updaterPage->hasChanges();
0447     } else if (ui->stackedWidget->currentWidget() == m_settingsPage) {
0448         changed = m_settingsPage->hasChanges();
0449     } else {
0450         changed = m_browseModel->hasChanges();
0451     }
0452 
0453     // if there are no changes don't ask the user
0454     if (!changed) {
0455         return true;
0456     }
0457 
0458     const int queryUser = KMessageBox::warningYesNoCancel(
0459         this,
0460         i18n("The settings of the current module have changed.\n"
0461              "Do you want to apply the changes or discard them?"),
0462         i18n("Apply Settings"),
0463         KStandardGuiItem::apply(),
0464         KStandardGuiItem::discard(),
0465         KStandardGuiItem::cancel());
0466 
0467     switch (queryUser) {
0468     case KMessageBox::Yes:
0469         save();
0470         return true;
0471     case KMessageBox::No:
0472         load();
0473         return true;
0474     case KMessageBox::Cancel:
0475         return false;
0476     default:
0477         return false;
0478     }
0479 }
0480 
0481 QString ApperKCM::page() const
0482 {
0483     return QString();
0484 }
0485 
0486 void ApperKCM::setPage(const QString &page)
0487 {
0488     auto transaction = qobject_cast<PkTransaction*>(ui->stackedWidget->currentWidget());
0489     if (transaction) {
0490         return;
0491     }
0492 
0493     if (page == QLatin1String("settings")) {
0494         if (ui->stackedWidget->currentWidget() != m_settingsPage) {
0495             if (!canChangePage()) {
0496                 return;
0497             }
0498 
0499             if (m_settingsPage == nullptr) {
0500                 m_settingsPage = new Settings(m_roles, this);
0501                 connect(m_settingsPage, &Settings::changed, this, &ApperKCM::checkChanged);
0502                 connect(m_settingsPage, &Settings::refreshCache, this, &ApperKCM::refreshCache);
0503                 ui->stackedWidget->addWidget(m_settingsPage);
0504 
0505                 connect(ui->generalSettingsPB, &QPushButton::toggled, m_settingsPage, &Settings::showGeneralSettings);
0506                 connect(ui->repoSettingsPB, &QPushButton::toggled, m_settingsPage, &Settings::showRepoSettings);
0507             }
0508             checkChanged();
0509 //            ui->buttonBox->clear();
0510 //            ui->buttonBox->setStandardButtons(QDialogButtonBox::Apply | QDialogButtonBox::Reset);
0511 //            setButtons(KCModule::Default | KCModule::Apply);
0512             emit changed(true); // THIS IS DUMB setButtons only take effect after changed goes true
0513             emit changed(false);
0514             ui->generalSettingsPB->setChecked(true);
0515             ui->stackedWidgetBar->setCurrentIndex(BAR_SETTINGS);
0516             ui->stackedWidget->setCurrentWidget(m_settingsPage);
0517             m_settingsPage->load();
0518             ui->titleL->clear();
0519             ui->backTB->setEnabled(true);
0520             emit caption(i18n("Settings"));
0521         }
0522     } else if (page == QLatin1String("updates")) {
0523         if (ui->stackedWidget->currentWidget() != m_updaterPage) {
0524             if (!canChangePage()) {
0525                 return;
0526             }
0527 
0528             if (m_updaterPage == nullptr) {
0529                 m_updaterPage = new Updater(m_roles, this);
0530                 connect(m_updaterPage, &Updater::refreshCache, this, &ApperKCM::refreshCache);
0531                 connect(m_updaterPage, &Updater::downloadSize, ui->downloadL, &QLabel::setText);
0532 //                connect(m_updaterPage, &Updater::changed, this, &ApperKCM::checkChanged);
0533                 ui->stackedWidget->addWidget(m_updaterPage);
0534                 ui->checkUpdatesPB->setIcon(QIcon::fromTheme(QLatin1String("view-refresh")));
0535                 connect(ui->checkUpdatesPB, &QPushButton::clicked, this, &ApperKCM::refreshCache);
0536 
0537                 ui->updatePB->setIcon(QIcon::fromTheme(QLatin1String("system-software-update")));
0538                 connect(ui->updatePB, &QPushButton::clicked, this, &ApperKCM::save);
0539                 connect(m_updaterPage, &Updater::changed, ui->updatePB, &QPushButton::setEnabled);
0540             }
0541 
0542             checkChanged();
0543             ui->stackedWidget->setCurrentWidget(m_updaterPage);
0544             m_updaterPage->load();
0545             ui->stackedWidgetBar->setCurrentIndex(BAR_UPDATE);
0546             ui->backTB->setEnabled(true);
0547             emit caption(i18n("Updates"));
0548         }
0549     } else if (page == QLatin1String("home")) {
0550         if (ui->stackedWidget->currentWidget() == m_updaterPage ||
0551             ui->stackedWidget->currentWidget() == m_settingsPage) {
0552             on_backTB_clicked();
0553         }
0554     } else if (page == QLatin1String("history")) {
0555         m_history = new TransactionHistory(this);
0556         ui->searchKLE->clear();
0557         connect(ui->searchKLE, &QLineEdit::textChanged, m_history, &TransactionHistory::setFilterRegExp);
0558         ui->stackedWidget->addWidget(m_history);
0559         ui->stackedWidget->setCurrentWidget(m_history);
0560         ui->backTB->setEnabled(true);
0561         ui->filtersTB->setEnabled(false);
0562         ui->widget->setEnabled(false);
0563         emit caption(i18n("History"));
0564     }
0565 }
0566 
0567 void ApperKCM::on_backTB_clicked()
0568 {
0569     bool canGoBack = false;
0570     if (ui->stackedWidget->currentWidget() == ui->pageBrowse) {
0571         if (!ui->browseView->goBack()) {
0572             return;
0573         } else if (m_groupsModel->hasParent()) {
0574             canGoBack = true;
0575         }
0576     } else if (ui->stackedWidget->currentWidget() == m_history) {
0577         ui->filtersTB->setEnabled(true);
0578         ui->widget->setEnabled(true);
0579         m_history->deleteLater();
0580         m_history = nullptr;
0581     } else if (ui->stackedWidget->currentWidget() == ui->pageHome) {
0582         if (m_groupsModel->setParentIndex()) {
0583             // if we are able to set a new parent item
0584             // do not disable back button
0585             return;
0586         }
0587     } else if (ui->stackedWidget->currentWidget() == m_updaterPage) {
0588         if (!canChangePage()) {
0589             return;
0590         }
0591         ui->stackedWidgetBar->setCurrentIndex(BAR_SEARCH);
0592         checkChanged();
0593     } else if (ui->stackedWidget->currentWidget() == m_settingsPage) {
0594         if (!canChangePage()) {
0595             return;
0596         }
0597         emit changed(true); // THIS IS DUMB setButtons only take effect after changed goes true
0598         ui->stackedWidgetBar->setCurrentIndex(BAR_SEARCH);
0599         checkChanged();
0600     }
0601 
0602     ui->homeView->selectionModel()->clear();
0603     ui->stackedWidget->setCurrentWidget(ui->pageHome);
0604     ui->backTB->setEnabled(canGoBack);
0605     // reset the search role
0606     m_searchRole = Transaction::RoleUnknown;
0607     emit caption();
0608 }
0609 
0610 void ApperKCM::showReviewPages()
0611 {
0612     m_changesModel->clear();
0613     m_changesModel->addSelectedPackagesFromModel(m_browseModel);
0614     ui->stackedWidget->setCurrentWidget(ui->pageChanges);
0615     ui->backTB->setEnabled(true);
0616     emit caption(i18n("Pending Changes"));
0617 }
0618 
0619 void ApperKCM::disconnectTransaction()
0620 {
0621     if (m_searchTransaction) {
0622         // Disconnect everything so that the model don't store
0623         // wrong data
0624         m_searchTransaction->cancel();
0625         disconnect(m_searchTransaction, &Transaction::finished, ui->browseView->busyCursor(), &KPixmapSequenceOverlayPainter::stop);
0626         disconnect(m_searchTransaction, &Transaction::finished, this, &ApperKCM::finished);
0627         disconnect(m_searchTransaction, &Transaction::finished, m_browseModel, &PackageModel::finished);
0628         disconnect(m_searchTransaction, &Transaction::finished, m_browseModel, &PackageModel::fetchSizes);
0629         disconnect(m_searchTransaction, &Transaction::package, m_browseModel, &PackageModel::addNotSelectedPackage);
0630         disconnect(m_searchTransaction, &Transaction::errorCode, this, &ApperKCM::errorCode);
0631     }
0632 }
0633 
0634 void ApperKCM::search()
0635 {
0636     ui->browseView->cleanUi();
0637     if (ui->stackedWidgetBar->currentIndex() != BAR_SEARCH) {
0638         ui->stackedWidgetBar->setCurrentIndex(BAR_SEARCH);
0639     }
0640 
0641     disconnectTransaction();
0642 
0643     // search
0644     switch (m_searchRole) {
0645     case Transaction::RoleSearchName:
0646         m_searchTransaction = Daemon::searchNames(m_searchString, m_filtersMenu->filters());
0647         emit caption(m_searchString);
0648         break;
0649     case Transaction::RoleSearchDetails:
0650         m_searchTransaction = Daemon::searchDetails(m_searchString, m_filtersMenu->filters());
0651         emit caption(m_searchString);
0652         break;
0653     case Transaction::RoleSearchFile:
0654         m_searchTransaction = Daemon::searchFiles(m_searchString, m_filtersMenu->filters());
0655         emit caption(m_searchString);
0656         break;
0657     case Transaction::RoleSearchGroup:
0658         if (m_searchGroupCategory.isEmpty()) {
0659             m_searchTransaction = Daemon::searchGroup(m_searchGroup, m_filtersMenu->filters());
0660             // m_searchString has the group nice name
0661             emit caption(m_searchString);
0662         } else {
0663             ui->browseView->setParentCategory(m_searchParentCategory);
0664             emit caption(m_searchParentCategory.data().toString());
0665 #ifndef HAVE_APPSTREAM
0666             if (m_searchGroupCategory.startsWith(QLatin1Char('@')) ||
0667                 m_searchGroupCategory.startsWith(QLatin1String("repo:"))) {
0668                 m_searchTransaction = Daemon::searchGroup(m_searchGroupCategory, m_filtersMenu->filters());
0669             }
0670 #endif
0671             // else the transaction is useless
0672         }
0673         break;
0674     case Transaction::RoleGetPackages:
0675         // we want all the installed ones
0676         ui->browseView->disableExportInstalledPB();
0677         m_searchTransaction = Daemon::getPackages(Transaction::FilterInstalled | m_filtersMenu->filters());
0678         connect(m_searchTransaction, &Transaction::finished, ui->browseView, &BrowseView::enableExportInstalledPB);
0679         emit caption(i18n("Installed Software"));
0680         break;
0681     case Transaction::RoleResolve:
0682 #ifdef HAVE_APPSTREAM
0683         if (!m_searchCategory.isEmpty()) {
0684             ui->browseView->setParentCategory(m_searchParentCategory);
0685             // WARNING the resolve might fail if the backend
0686             // has a low limit MaximumItemsToResolve
0687             m_searchTransaction = Daemon::resolve(m_searchCategory, m_filtersMenu->filters());
0688             emit caption(m_searchParentCategory.data().toString());
0689         } else {
0690             ui->browseView->setParentCategory(m_searchParentCategory);
0691             KMessageBox::error(this, i18n("Could not find an application that matched this category"));
0692             emit caption();
0693             disconnectTransaction();
0694             m_searchTransaction = 0;
0695             return;
0696         }
0697         break;
0698 #endif
0699     default:
0700         qCWarning(APPER) << "Search type not defined yet";
0701         emit caption();
0702         disconnectTransaction();
0703         m_searchTransaction = nullptr;
0704         return;
0705     }
0706     connect(m_searchTransaction, &Transaction::finished, ui->browseView->busyCursor(), &KPixmapSequenceOverlayPainter::stop);
0707     connect(m_searchTransaction, &Transaction::finished, this, &ApperKCM::finished);
0708     connect(m_searchTransaction, &Transaction::finished, m_browseModel, &PackageModel::finished);
0709 
0710     if (ui->browseView->isShowingSizes()) {
0711         connect(m_searchTransaction, &Transaction::finished, m_browseModel, &PackageModel::fetchSizes);
0712     }
0713     connect(m_searchTransaction, &Transaction::package, m_browseModel, &PackageModel::addNotSelectedPackage);
0714     connect(m_searchTransaction, &Transaction::errorCode, this, &ApperKCM::errorCode);
0715 
0716     // cleans the models
0717     m_browseModel->clear();
0718 
0719     ui->browseView->showInstalledPanel(m_searchRole == Transaction::RoleGetPackages);
0720     ui->browseView->busyCursor()->start();
0721 
0722     ui->backTB->setEnabled(true);
0723     setCurrentActionCancel(true);
0724     setCurrentActionEnabled(m_searchTransaction->allowCancel());
0725 
0726     ui->stackedWidget->setCurrentWidget(ui->pageBrowse);
0727 }
0728 
0729 void ApperKCM::changed()
0730 {
0731     Transaction *trans = qobject_cast<Transaction*>(sender());
0732     setCurrentActionEnabled(trans->allowCancel());
0733 }
0734 
0735 void ApperKCM::refreshCache()
0736 {
0737     emit changed(false);
0738 
0739     QWidget *currentWidget = ui->stackedWidget->currentWidget();
0740 
0741     auto transactionW = new PkTransactionWidget(this);
0742     connect(transactionW, &PkTransactionWidget::titleChangedProgress, this, &ApperKCM::caption);
0743     QPointer<PkTransaction> transaction = new PkTransaction(transactionW);
0744     Daemon::setHints(QLatin1String("cache-age=")+QString::number(m_cacheAge));
0745     transaction->refreshCache(m_forceRefreshCache);
0746     transactionW->setTransaction(transaction, Transaction::RoleRefreshCache);
0747 
0748     ui->stackedWidget->addWidget(transactionW);
0749     ui->stackedWidget->setCurrentWidget(transactionW);
0750     ui->stackedWidgetBar->setCurrentIndex(BAR_TITLE);
0751     ui->backTB->setEnabled(false);
0752     connect(transactionW, &PkTransactionWidget::titleChanged, ui->titleL, &QLabel::setText);
0753 
0754     QEventLoop loop;
0755     connect(transaction, &PkTransaction::finished, &loop, &QEventLoop::quit);
0756 
0757     // wait for the end of transaction
0758     if (!transaction->isFinished()) {
0759         loop.exec();
0760         if (!transaction) {
0761             // Avoid crashing
0762             return;
0763         }
0764 
0765         // If the refresh failed force next refresh Cache call
0766         m_forceRefreshCache = transaction->exitStatus() == PkTransaction::Failed;
0767     }
0768 
0769     if (m_updaterPage) {
0770         m_updaterPage->getUpdates();
0771     }
0772 
0773     if (currentWidget == m_settingsPage) {
0774         setPage(QLatin1String("settings"));
0775     } else {
0776         setPage(QLatin1String("updates"));
0777     }
0778 
0779     QTimer::singleShot(0, this, &ApperKCM::checkChanged);
0780 }
0781 
0782 void ApperKCM::save()
0783 {
0784     QWidget *currentWidget = ui->stackedWidget->currentWidget();
0785     if (currentWidget == m_settingsPage) {
0786         m_settingsPage->save();
0787     } else {
0788         ui->reviewMessage->hide();
0789 
0790         auto transactionW = new PkTransactionWidget(this);
0791         connect(transactionW, &PkTransactionWidget::titleChangedProgress, this, &ApperKCM::caption);
0792         QPointer<PkTransaction> transaction = new PkTransaction(transactionW);
0793 
0794         ui->stackedWidget->addWidget(transactionW);
0795         ui->stackedWidget->setCurrentWidget(transactionW);
0796         ui->stackedWidgetBar->setCurrentIndex(BAR_TITLE);
0797         ui->backTB->setEnabled(false);
0798         connect(transactionW, &PkTransactionWidget::titleChanged, ui->titleL, &QLabel::setText);
0799         emit changed(false);
0800 
0801         QEventLoop loop;
0802         connect(transaction, &PkTransaction::finished, &loop, &QEventLoop::quit);
0803         if (currentWidget == m_updaterPage) {
0804             transaction->updatePackages(m_updaterPage->packagesToUpdate());
0805             transactionW->setTransaction(transaction, Transaction::RoleUpdatePackages);
0806 
0807             // wait for the end of transaction
0808             if (!transaction->isFinished()) {
0809                 loop.exec();
0810                 if (!transaction) {
0811                     // Avoid crashing
0812                     return;
0813                 }
0814             }
0815         } else {
0816             // install then remove packages
0817             QStringList installPackages = m_browseModel->selectedPackagesToInstall();
0818             if (!installPackages.isEmpty()) {
0819                 transaction->installPackages(installPackages);
0820                 transactionW->setTransaction(transaction, Transaction::RoleInstallPackages);
0821 
0822                 // wait for the end of transaction
0823                 if (!transaction->isFinished()) {
0824                     loop.exec();
0825                     if (!transaction) {
0826                         // Avoid crashing
0827                         return;
0828                     }
0829                 }
0830 
0831                 if (transaction->exitStatus() == PkTransaction::Success) {
0832                     m_browseModel->uncheckAvailablePackages();
0833                 }
0834             }
0835 
0836             QStringList removePackages = m_browseModel->selectedPackagesToRemove();
0837             if (!removePackages.isEmpty()) {
0838                 transaction->removePackages(removePackages);
0839                 transactionW->setTransaction(transaction, Transaction::RoleRemovePackages);
0840 
0841                 // wait for the end of transaction
0842                 if (!transaction->isFinished()) {
0843                     loop.exec();
0844                     if (!transaction) {
0845                         // Avoid crashing
0846                         return;
0847                     }
0848                 }
0849 
0850                 if (transaction->exitStatus() == PkTransaction::Success) {
0851                     m_browseModel->uncheckInstalledPackages();
0852                 }
0853             }
0854         }
0855 
0856         transaction->deleteLater();
0857         if (currentWidget == m_updaterPage) {
0858             m_updaterPage->getUpdates();
0859             setPage(QLatin1String("updates"));
0860         } else {
0861             // install then remove packages
0862             search();
0863         }
0864         QTimer::singleShot(0, this, &ApperKCM::checkChanged);
0865     }
0866 }
0867 
0868 void ApperKCM::load()
0869 {
0870     if (ui->stackedWidget->currentWidget() == m_updaterPage) {
0871         m_updaterPage->load();
0872     } else if (ui->stackedWidget->currentWidget() == m_settingsPage) {
0873         m_settingsPage->load();
0874     } else {
0875         // set focus on the search lineEdit
0876         ui->searchKLE->setFocus(Qt::OtherFocusReason);
0877         m_browseModel->setAllChecked(false);
0878     }
0879 }
0880 
0881 void ApperKCM::defaults()
0882 {
0883     if (ui->stackedWidget->currentWidget() == m_settingsPage) {
0884         m_settingsPage->defaults();
0885     }
0886 }
0887 
0888 void ApperKCM::finished()
0889 {
0890     // if m_currentAction is false means that our
0891     // find button should be disable as there aren't any
0892     // search methods
0893     setCurrentActionEnabled(m_currentAction);
0894     setCurrentActionCancel(false);
0895     m_searchTransaction = nullptr;
0896 }
0897 
0898 void ApperKCM::keyPressEvent(QKeyEvent *event)
0899 {
0900     if (ui->searchKLE->hasFocus() &&
0901         ui->stackedWidget->currentWidget() != m_history &&
0902         (event->key() == Qt::Key_Return ||
0903          event->key() == Qt::Key_Enter)) {
0904         // special tab handling here
0905         m_currentAction->trigger();
0906         return;
0907     }
0908 //    KCModule::keyPressEvent(event);
0909 }
0910 
0911 void ApperKCM::closeEvent(QCloseEvent *event)
0912 {
0913 //     PkTransaction *transaction = qobject_cast<PkTransaction*>(stackedWidget->currentWidget());
0914 //     if (transaction) {
0915         event->ignore();
0916 //     } else {
0917 //         event->accept();
0918 //     }
0919 }
0920 
0921 #include "moc_ApperKCM.cpp"