view ui/mainwindow.cpp @ 631:edf269b6e499

Add self test as first opertation on start
author Andre Heinecke <andre.heinecke@intevation.de>
date Mon, 23 Jun 2014 13:05:08 +0200
parents 292c590ba9cb
children 129e611eaf50
line wrap: on
line source
/* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU GPL (v>=2)
 * and comes with ABSOLUTELY NO WARRANTY!
 * See LICENSE.txt for details.
 */
#include "mainwindow.h"

#include <QDebug>
#include <QProcess>
#include <QProgressDialog>
#include <QMessageBox>
#include <QSystemTrayIcon>
#include <QAction>
#include <QDialog>
#include <QDir>
#include <QMenu>
#include <QApplication>
#include <QFile>
#include <QTimer>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QGroupBox>
#include <QSplitter>
#include <QLabel>
#include <QImage>
#include <QCheckBox>

#include "certificatelist.h"
#include "downloader.h"
#include "helpdialog.h"
#include "aboutdialog.h"
#include "certificateitemdelegate.h"
#include "separatoritemdelegate.h"
#include "installwrapper.h"
#include "util.h"
#include "logging.h"
#include "binverify.h"
#include "processhelp.h"
#include "processwaitdialog.h"

// The amount of time in minutes stay silent if we have
// something to say
#define NAG_INTERVAL_MINUTES 70

#ifndef APPNAME
#define APPNAME "TrustBridge"
#endif

#define SERVER_URL "https://files.intevation.de:443"

#ifdef RELEASE_BUILD
# define LIST_RESOURCE "/users/aheinecke/zertifikatsliste.txt"
# define SW_RESOURCE   "/users/aheinecke/TrustBridge.exe"
# ifdef Q_OS_WIN
#  define SW_RESOURCE_VERSION "/users/aheinecke/TrustBridge-%1.exe"
# else
#  define SW_RESOURCE_VERSION "/users/aheinecke/TrustBridge-%1.sh"
# endif
#else // RELEASE_BUILD
# define LIST_RESOURCE "/users/aheinecke/development/zertifikatsliste.txt"
# define SW_RESOURCE   "/users/aheinecke/development/TrustBridge.exe"
# ifdef Q_OS_WIN
#  define SW_RESOURCE_VERSION "/users/aheinecke/development/TrustBridge-development.exe"
# else
#  define SW_RESOURCE_VERSION "/users/aheinecke/development/TrustBridge-development.sh"
# endif
#endif

MainWindow::MainWindow(bool trayMode):
    mTrayMode(trayMode)
{
    createActions();
    createTrayIcon();
    createMenuBar();
    createContent();
    resize(950, 540);
    qRegisterMetaType<SSLConnection::ErrorCode>("SSLConnection::ErrorCode");
    qRegisterMetaType<Certificate::Status>("Certificate::Status");

    connect(mTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
            this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));

    mMessageTimer = new QTimer(this);
    connect(mMessageTimer, SIGNAL(timeout()), this, SLOT(showMessage()));
    mMessageTimer->setInterval(NAG_INTERVAL_MINUTES * 60 * 1000);
    mMessageTimer->start();
    checkUpdates();
    loadUnselectedCertificates();
    loadCertificateList();
    if (!trayMode) {
        show();
    }
}

void MainWindow::iconActivated(QSystemTrayIcon::ActivationReason reason)
{
    switch (reason) {
    case QSystemTrayIcon::Trigger:
    case QSystemTrayIcon::MiddleClick:
        showMessage();
        break;
    case QSystemTrayIcon::DoubleClick:
        show();
        break;
    default:
        ;
    }
}

void MainWindow::messageClicked()
{
    if (mCurState == NewListAvailable) {
        show();
    }

    if (mCurState == NewSoftwareAvailable) {
        verifySWData();
        QString swFileName = mSettings.value("Software/available").toString();
        if (swFileName.isEmpty()) {
            checkUpdates(true);
            mCurState = DownloadingSW;
            return;
        }
        installNewSW(swFileName,
                mSettings.value("Software/availableDate").toDateTime());
    }
}

void MainWindow::showMessage()
{
    if (mCurMessage.isEmpty()) {
        return;
    }
    if (mCurState == NewSoftwareAvailable || !isVisible()) {
        mTrayIcon->showMessage(QApplication::applicationName(), mCurMessage,
                               QSystemTrayIcon::Information, 10000);
        mMessageTimer->start(); // Restart the timer so that we don't spam
    }
}

void MainWindow::verifyListData()
{
    QString availableFileName = mSettings.value("List/available").toString();
    QString installedFileName = mSettings.value("List/installed").toString();
    if (!availableFileName.isEmpty()) {
        mListToInstall.readList(availableFileName.toLocal8Bit().constData());
        if (!mListToInstall.isValid()) {
            mCurState = TransferError;
            // Probably a bug when Qt fileName is encoded and cFileName
            // fails because of this. This needs a unit test!
            // Maybe check that the file is in our data directory
            QFile::remove(availableFileName);
            mSettings.remove("List/available");
            mSettings.remove("List/availableDate");
        }
    } else {
        // Make sure the available notation is also removed
        mSettings.remove("List/available");
        mSettings.remove("List/availableDate");
    }

    if (!installedFileName.isEmpty()) {
        mInstalledList.readList(installedFileName.toLocal8Bit().constData());
        if (!mInstalledList.isValid()) {
            // Probably a bug when Qt fileName is encoded and cFileName
            // fails because of this. This needs a unit test!
            // Maybe check that the file is in our data directory
            QFile::remove(installedFileName);
            mSettings.remove("List/installed");
            mSettings.remove("List/installedDate");
        }
    } else {
        mSettings.remove("List/installed");
        mSettings.remove("List/installedDate");
    }
}

void MainWindow::verifySWData()
{
    QString swFileName = mSettings.value("Software/available").toString();

    if (swFileName.isEmpty()) {
        mSettings.remove("Software/availableDate");
        return;
    }

    QFileInfo fi(swFileName);
    if (!fi.exists()) {
        mSettings.remove("Software/available");
        mSettings.remove("Software/availableDate");
        qDebug() << "Software does not yet exist.";
        return;
    }
    if (!fi.isExecutable()) {
        qWarning() << "Downloaded file: " << swFileName << " is not executable.";
        QFile::remove(swFileName);
        mSettings.remove("Software/available");
        mSettings.remove("Software/availableDate");
        return;
    }
    bin_verify_result verifyResult = verify_binary(swFileName.toUtf8().constData(),
            swFileName.toUtf8().size());
    qDebug() << "Binary verify result: " << verifyResult;
    if (verifyResult != VerifyValid) {
        qDebug() << "Failed to verify downloaded data.";
        QFile::remove(swFileName);
        mSettings.remove("Software/available");
        mSettings.remove("Software/availableDate");
        return;
    }
}

void MainWindow::handleNewList(const QString& fileName, const QDateTime& modDate) {
    mSettings.setValue("List/available", fileName);
    mSettings.setValue("List/availableDate", modDate);

    verifyListData();
    if (!mListToInstall.isValid()) {
        /* Downloader provided invalid files */
        /* TODO: Error count. Error handling. Otherwise
         * we can go into an endless loop here */

        /* Retry the download again in 10 - 20 minutes */
        QTimer::singleShot(600000 + (qrand() % 60000), this, SLOT(checkUpdates()));
    } else {
        mCurMessage = tr("An updated certificate list is available. Click here to install.");
        setState(NewListAvailable);
        showMessage();
        loadCertificateList();
    }
}

void MainWindow::handleNewSW(const QString& fileName, const QDateTime& modDate) {
    mCurMessage = tr("An update for %1 is available.\n"
            "Click here to download and install the update.").arg(
                QApplication::applicationName());
    setState(NewSoftwareAvailable);
    mSettings.setValue("Software/available", fileName);
    mSettings.setValue("Software/availableDate", modDate);

    mSettings.sync();
    showMessage();
}

void MainWindow::installNewSW(const QString& fileName, const QDateTime& modDate) {
    QFileInfo instProcInfo = QFileInfo(fileName);
    QString filePath = QDir::toNativeSeparators(instProcInfo.absoluteFilePath());

    if (verify_binary(filePath.toUtf8().constData(),
            filePath.toUtf8().size()) != VerifyValid) {
        qDebug() << "Invalid software. Not installing";
        return;
      }
    QFileInfo fi(QCoreApplication::applicationFilePath());
    QDir installDir = fi.absoluteDir();

#ifdef WIN32
    QString parameters = QString::fromLatin1("/S /UPDATE=1 /D=") +
        installDir.path().replace("/", "\\") + "";

    SHELLEXECUTEINFOW shExecInfo;
    memset (&shExecInfo, 0, sizeof(SHELLEXECUTEINFOW));
    shExecInfo.cbSize = sizeof(SHELLEXECUTEINFOW);

    shExecInfo.lpFile = reinterpret_cast<LPCWSTR> (filePath.utf16());
    shExecInfo.lpParameters = reinterpret_cast<LPCWSTR> (parameters.utf16());

  //  shExecInfo.fMask = SEE_MASK_NOASYNC;
    shExecInfo.nShow = SW_SHOWDEFAULT;

    if (!is_admin()) {
        shExecInfo.lpVerb = L"open";
    } else {
        shExecInfo.lpVerb = L"runas";
    }

    qDebug() << "Starting process: " << filePath
             << " with arguments: " << parameters;

    if (!ShellExecuteExW(&shExecInfo)) {
        /* Execution failed, maybe the user aborted the UAC check? */
        char* errmsg = getLastErrorMsg();
        QString qerrmsg = QString::fromUtf8(errmsg);
        free(errmsg);
        qDebug() << "Failed to start process: " << qerrmsg;
        setState(NewSoftwareAvailable);
        return;
    }
#else /* WIN32 */
    QStringList parameters;
    parameters << "/S" << "/UPDATE=1"
               << QString::fromLatin1("/D=%1").arg(installDir.path());

    qDebug() << "Starting process " << filePath << " args: " << parameters;

    if (!QProcess::startDetached(filePath, parameters)) {
        qDebug() << "Failed to start process.";
        return;
    }
#endif
    /* Installer process should now be running. We exit */

    closeApp();
}

void MainWindow::checkUpdates(bool downloadSW)
{
    verifyListData();

    if (!mSettings.contains("Software/installedDate") ||
          mSettings.value("Software/installedVersion").toString() != QApplication::applicationVersion()) {
        /* This should only happen on initial startup and after an update has
         * been installed */
        getLastModForCurrentVersion();
        return;
    }
    QDateTime listInstalledLastMod = mSettings.value("List/installedDate").toDateTime();
    QDateTime swInstalledLastMod = mSettings.value("Software/installedDate").toDateTime();

    QString listResource = QString::fromLatin1(LIST_RESOURCE);
    QString swResource = QString::fromLatin1(SW_RESOURCE);

#ifndef RELEASE_BUILD
    /* Use this for testing to specify another file name for updates */
    listResource = mSettings.value("List/resource", listResource).toString();
    swResource = mSettings.value("Software/resource", swResource).toString();
#endif


    Downloader* downloader = new Downloader(this,
                                            QString::fromLatin1(SERVER_URL),
                                            QByteArray(),
                                            swInstalledLastMod,
                                            listInstalledLastMod,
                                            swResource,
                                            listResource,
                                            downloadSW);

    connect(downloader, SIGNAL(newListAvailable(const QString&, const QDateTime&)),
            this, SLOT(handleNewList(const QString&, const QDateTime&)));
    if (!downloadSW) {
        connect(downloader, SIGNAL(newSoftwareAvailable(const QString&, const QDateTime&)),
                this, SLOT(handleNewSW(const QString&, const QDateTime&)));
    } else {
        connect(downloader, SIGNAL(newSoftwareAvailable(const QString&, const QDateTime&)),
                this, SLOT(installNewSW(const QString&, const QDateTime&)));
    }

    connect(downloader, SIGNAL(finished()), downloader, SLOT(deleteLater()));
    connect(downloader, SIGNAL(error(const QString &, SSLConnection::ErrorCode)),
            this, SLOT(downloaderError(const QString &, SSLConnection::ErrorCode)));
    downloader->start();
}

void MainWindow::getLastModForCurrentVersion()
{
    QString softwareVersion = QString::fromLatin1(SW_RESOURCE_VERSION).arg(
        QApplication::applicationVersion());
    qDebug() << softwareVersion;
    QString listResource = QString::fromLatin1(LIST_RESOURCE);
    Downloader* downloader = new Downloader(this,
                                            QString::fromLatin1(SERVER_URL),
                                            QByteArray(),
                                            QDateTime::currentDateTime(),
                                            QDateTime::currentDateTime(),
                                            softwareVersion,
                                            listResource,
                                            false);
    connect(downloader, SIGNAL(finished()), downloader, SLOT(deleteLater()));
    connect(downloader, SIGNAL(error(const QString &, SSLConnection::ErrorCode)),
            this, SLOT(downloaderError(const QString &, SSLConnection::ErrorCode)));
    connect(downloader, SIGNAL(lastModifiedDate(const QDateTime&)),
        this, SLOT(setLastModifiedSWDate(const QDateTime&)));

    downloader->start();
}

void MainWindow::setLastModifiedSWDate(const QDateTime &date)
{
    mSettings.beginGroup("Software");
    mSettings.setValue("installedDate", date);
    mSettings.setValue("installedVersion", QApplication::applicationVersion());
    mSettings.endGroup();
    checkUpdates();
}

void MainWindow::downloaderError(const QString &message, SSLConnection::ErrorCode error)
{
    /* TODO logging and handle error according to a plan */
    mCurMessage = message;
    showMessage();
    setState(TransferError);
}

void MainWindow::createActions()
{
    mCheckUpdates = new QAction(tr("Check for Updates"), this);
    connect(mCheckUpdates, SIGNAL(triggered()), this, SLOT(checkUpdates()));
    mQuitAction = new QAction(tr("Quit"), this);
    connect(mQuitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
}

void MainWindow::createTrayIcon()
{
    QIcon trayImg(":/img/tray_22.png");

    mTrayMenu = new QMenu(this);
    mTrayMenu->addAction(mCheckUpdates);
    mTrayMenu->addAction(mQuitAction);

    mTrayIcon = new QSystemTrayIcon(this);
    mTrayIcon->setContextMenu(mTrayMenu);

    mTrayIcon->setIcon(trayImg);
    setWindowIcon(trayImg);
    mTrayIcon->show();
    mTrayIcon->setToolTip(tr("TrustBridge"));

    connect(mTrayIcon, SIGNAL(messageClicked()), this, SLOT(messageClicked()));
}

void MainWindow::createMenuBar()
{
    mMenuBar = new QMenuBar(this);
    QMenu *mMenu = new QMenu(tr("Menu"), mMenuBar);
    mMenuBar->addMenu(mMenu);
    QAction *update = mMenu->addAction(tr("Force Update"));
    mMenu->addSeparator();
    QAction *help = mMenu->addAction(tr("Help"));
    QAction *about = mMenu->addAction(tr("About"));
    mMenu->addSeparator();
    QAction *quit = mMenu->addAction(tr("Quit"));
    connect(update, SIGNAL(triggered()), this, SLOT(checkUpdates()));
    connect(help, SIGNAL(triggered()), this, SLOT(showHelp()));
    connect(about, SIGNAL(triggered()), this, SLOT(showAbout()));
    connect(quit, SIGNAL(triggered()), this, SLOT(closeApp()));
    setMenuBar(mMenuBar);
}

void MainWindow::createContent()
{
    // Create a central widget containing the main layout.
    QWidget *base = new QWidget;

    // Layouts and Container
    QHBoxLayout *mainLayout = new QHBoxLayout;
    QVBoxLayout *infoLayout = new QVBoxLayout;
    QVBoxLayout *certLayout = new QVBoxLayout;
    QHBoxLayout *detailLayout = new QHBoxLayout;
    QVBoxLayout *detailLabelLayout = new QVBoxLayout;
    QVBoxLayout *detailContentLayout = new QVBoxLayout;
    QHBoxLayout *headerLayout = new QHBoxLayout;
    QVBoxLayout *headerTextLayout = new QVBoxLayout;
    QHBoxLayout *bottomLayout = new QHBoxLayout;
    QVBoxLayout *settingsLayout = new QVBoxLayout;
    QVBoxLayout *listInfoLayout = new QVBoxLayout;

    // The certificate list
    QGroupBox *certBox = new QGroupBox(tr("Managed Certificates"));
    mCertListWidget = new QListWidget;
    connect(mCertListWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)),
        this, SLOT(showDetails(QListWidgetItem*)));
    certLayout->addWidget(mCertListWidget);

    mCurrentListDate = new QLabel(tr("Current List Date: %1").arg(""));
    mNewListDate = new QLabel("");
    listInfoLayout->addWidget(mCurrentListDate);
    listInfoLayout->addWidget(mNewListDate);
    certLayout->addLayout(listInfoLayout);

    certBox->setLayout(certLayout);

    // The header (icon, about text)
    QImage *logoImage = new QImage(":/img/logo.png");
    QLabel *logo = new QLabel;
    logo->setBackgroundRole(QPalette::Base);
    logo->setPixmap(QPixmap::fromImage(*logoImage));
    QLabel *title = new QLabel("<h2>" + QString::fromLatin1(APPNAME) + "</h2>");
    QLabel *subTitle = new QLabel("This Software installs and removes Certificates");
    headerTextLayout->addWidget(title);
    headerTextLayout->addWidget(subTitle);
    headerLayout->addWidget(logo);
    headerLayout->addLayout(headerTextLayout);
    headerLayout->setStretch(0, 0);
    headerLayout->setStretch(1, 10);

    // The settings.
    QGroupBox *settingsBox = new QGroupBox(tr("Settings"));
    mAutoUpdateOption = new QCheckBox(tr("Autoupdate"));
    mAutoStartOption = new QCheckBox(tr("Autostart"));
    settingsLayout->addWidget(mAutoUpdateOption);
    settingsLayout->addWidget(mAutoStartOption);
    settingsBox->setLayout(settingsLayout);
    connect(mAutoUpdateOption, SIGNAL(stateChanged(int)),
        this, SLOT(saveAutoUpdate(int)));
    connect(mAutoStartOption, SIGNAL(stateChanged(int)),
        this, SLOT(saveAutoStart(int)));
    mSettings.beginGroup("settings");
    bool autoUpdate = mSettings.value("autoupdate", false).toBool();
    bool autoStart = mSettings.value("autostart", true).toBool();
    mSettings.endGroup();
    mAutoUpdateOption->setCheckState(autoUpdate ? Qt::Checked : Qt::Unchecked);
    mAutoStartOption->setCheckState(autoStart ? Qt::Checked : Qt::Unchecked);


    // The buttons.
    bottomLayout->setAlignment(Qt::AlignBottom);
    installButton = new QPushButton(tr("Install selected"));
    installButton->setFixedHeight(80);
    connect(installButton, SIGNAL(clicked()), this, SLOT(resizeButtons()));
    connect(installButton, SIGNAL(clicked()), this, SLOT(checkAndInstallCerts()));
    quitButton = new QPushButton(tr("Quit"));
    quitButton->setFixedHeight(20);
    connect(quitButton, SIGNAL(clicked()), this, SLOT(closeApp()));
    bottomLayout->insertStretch(0, 10);
    bottomLayout->addWidget(installButton);
    bottomLayout->setAlignment(installButton, Qt::AlignBottom);
    bottomLayout->addWidget(quitButton);
    bottomLayout->setAlignment(quitButton, Qt::AlignBottom);

#ifdef Q_OS_WIN
    if (is_admin()) {
        QIcon uacShield = QApplication::style()->standardIcon(QStyle::SP_VistaShield);
        installButton->setIcon(uacShield);
    }
#endif

    // The certificate details
    QGroupBox *detailBox = new QGroupBox(tr("Details"));
    QLabel *subjectCN = new QLabel(tr("Subject Common Name:"));
    QLabel *subjectOU = new QLabel(tr("Subject Organisation:"));
    QLabel *issuerCN = new QLabel(tr("Issuer Common Name:"));
    QLabel *issuerOU = new QLabel(tr("Issuer Organisation:"));
    QLabel *validFrom = new QLabel(tr("Valid from:"));
    QLabel *validTo = new QLabel(tr("Valid to:"));
    QLabel *fingerprint = new QLabel(tr("Fingerprint:"));
    detailLabelLayout->addWidget(subjectCN);
    detailLabelLayout->addWidget(subjectOU);
    detailLabelLayout->addWidget(issuerCN);
    detailLabelLayout->addWidget(issuerOU);
    detailLabelLayout->addWidget(validFrom);
    detailLabelLayout->addWidget(validTo);
    detailLabelLayout->addWidget(fingerprint);
    mSubjectCN = new QLabel(tr(""));
    mSubjectO = new QLabel(tr(""));
    mIssuerCN = new QLabel(tr(""));
    mIssuerO = new QLabel(tr(""));
    mValidFrom = new QLabel(tr(""));
    mValidTo = new QLabel(tr(""));
    mFingerprint = new QLabel(tr(""));
    mFingerprint->setFont(QFont("DejaVu Sans Mono"));
    detailContentLayout->addWidget(mSubjectCN);
    detailContentLayout->addWidget(mSubjectO);
    detailContentLayout->addWidget(mIssuerCN);
    detailContentLayout->addWidget(mIssuerO);
    detailContentLayout->addWidget(mValidFrom);
    detailContentLayout->addWidget(mValidTo);
    detailContentLayout->addWidget(mFingerprint);
    detailLayout->addLayout(detailLabelLayout);
    detailLayout->addLayout(detailContentLayout);
    detailBox->setLayout(detailLayout);

    infoLayout->addSpacing(20);
    infoLayout->addLayout(headerLayout);
    infoLayout->addWidget(detailBox);
    infoLayout->addWidget(settingsBox);
    infoLayout->addLayout(bottomLayout);

    mainLayout->addWidget(certBox);
    mainLayout->addLayout(infoLayout);
    mainLayout->setStretchFactor(certBox, 37);
    mainLayout->setStretchFactor(infoLayout, 63);

    // QMainWindow allready has a layout. All child layouts and widgets are
    // managed in the central widget.
    base->setLayout(mainLayout);
    setCentralWidget(base);
}

void MainWindow::loadCertificateList()
{
    mCertListWidget->clear();
    int i = 0;

    /* TODO: if nothing is available (neither old nor new) add some progress
     * indication */
    QList<Certificate> newInstallCerts;
    QList<Certificate> newRemoveCerts;
    QList<Certificate> oldInstallCerts;
    QList<Certificate> oldRemoveCerts;

    if (mListToInstall.getCertificates().isEmpty()) {
        // No new list available, add old certificates.
        foreach (const Certificate &cert, mInstalledList.getCertificates()) {
            if (cert.isInstallCert()) {
                oldInstallCerts.append(cert);
            }
            else {
                oldRemoveCerts.append(cert);
            }
        }
        // Set the date of the old list.
        mCurrentListDate->setText(tr("Current List Date: %1")
            .arg(mInstalledList.date().toString()));
    }
    else {
        // Sort and filter both lists.
        foreach (const Certificate &cert, mListToInstall.getCertificates()) {
            if (cert.isInstallCert()) {
                // Certificate with status "install".
                if (mInstalledList.getCertificates().contains(cert)) {
                    // Was in the old list.
                    oldInstallCerts.append(cert);
                }
                else {
                    // Is a brand new certificate
                    newInstallCerts.append(cert);
                }
            }
            else {
                // Certificate with status "remove".
                if (mInstalledList.getCertificates().contains(cert)) {
                    // Was in the old list.
                    oldRemoveCerts.append(cert);
                }
                else {
                    // Was in the old list with status "install" and now has the
                    // status "remove".
                    newRemoveCerts.append(cert);
                }
            }
        }
        mCurrentListDate->setText(tr("Current List Date: %1")
            .arg(mInstalledList.date().toString()));
        mNewListDate->setText(tr("New List Date: %1").arg(mListToInstall.date().toString()));
    }

    // Add separators and certificates to list widget.
    if (!newInstallCerts.isEmpty()) {
        mCertListWidget->addItem(createSeparator(tr("New certificates to install"), i++));
        foreach (const Certificate &cert, newInstallCerts) {
            mCertListWidget->addItem(createListItem(cert, Certificate::InstallNew, i++));
        }
    }

    if (!newRemoveCerts.isEmpty()) {
        mCertListWidget->addItem(createSeparator(tr("New certificates to remove"), i++));
        foreach (const Certificate &cert, newRemoveCerts) {
            mCertListWidget->addItem(createListItem(cert, Certificate::RemoveNew, i++));
        }
    }

    if (!oldInstallCerts.isEmpty()) {
        mCertListWidget->addItem(createSeparator(tr("Old certificates to install"), i++));
        foreach (const Certificate &cert, oldInstallCerts) {
            mCertListWidget->addItem(createListItem(cert, Certificate::InstallOld, i++));
        }
    }

    if (!oldRemoveCerts.isEmpty()) {
        mCertListWidget->addItem(createSeparator(tr("Old certificates to remove"), i++));
        foreach (const Certificate &cert, oldRemoveCerts) {
            mCertListWidget->addItem(createListItem(cert, Certificate::RemoveOld, i++));
        }
    }
}

QListWidgetItem* MainWindow::createSeparator(const QString &text, int index)
{
    SeparatorItemDelegate *separatorDelegate = new SeparatorItemDelegate();
    QListWidgetItem *separator = new QListWidgetItem(text);
    mCertListWidget->setItemDelegateForRow(index, separatorDelegate);
    separator->setFlags(separator->flags() ^ Qt::ItemIsUserCheckable);
    return separator;
}

QListWidgetItem* MainWindow::createListItem(const Certificate &certificate,
    Certificate::Status status, int index)
{
    CertificateItemDelegate *certDelegate = new CertificateItemDelegate();
    QListWidgetItem* item = new QListWidgetItem(certificate.shortDescription());
    item->setData(CertificateItemDelegate::DataRole,
        QVariant::fromValue(certificate));
    item->setData(CertificateItemDelegate::StatusRole, status);
    if (!mPreviouslyUnselected.contains(certificate.base64Line()) &&
        status == Certificate::RemoveOld) {
        item->setFlags(item->flags() ^ Qt::ItemIsUserCheckable);
    }
    else {
        Qt::CheckState checkedState =
            mPreviouslyUnselected.contains(certificate.base64Line()) ?
                Qt::Unchecked : Qt::Checked;
        item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
        item->setCheckState(checkedState);
    }
    mCertListWidget->setItemDelegateForRow(index, certDelegate);
    return item;
}

void MainWindow::showHelp()
{
    qDebug() << "show helpdialog";
    HelpDialog *help = new HelpDialog(this);
    help->show();
}

void MainWindow::showAbout()
{
    qDebug() << "show aboutdialog";
    AboutDialog *about = new AboutDialog(this);
    about->show();
}

void MainWindow::showDetails(QListWidgetItem *item)
{
    if (item == NULL) {
        return;
    }
    Certificate cert = item->data(CertificateItemDelegate::DataRole).value<Certificate>();
    mSubjectCN->setText(cert.subjectCN());
    mSubjectO->setText(cert.subjectO());
    mIssuerCN->setText(cert.issuerCN());
    mIssuerO->setText(cert.issuerO());
    mValidFrom->setText(cert.validFrom().toString());
    mValidTo->setText(cert.validTo().toString());
    mFingerprint->setText(cert.fingerprint());
}

void MainWindow::resizeButtons()
{
    installButton->setFixedHeight(20);
    quitButton->setFixedHeight(80);
}

void MainWindow::installerError(const QString& errMsg) {
    QMessageBox::warning(this, tr("Error executing update"), errMsg);
}

void MainWindow::installerSuccess() {
    if (mCurState == NewListAvailable) {
        mCurState = NothingChanged;
        mCurMessage = QString();

        QString listFileName = mSettings.value("List/available").toString();
        QDateTime listFileDate = mSettings.value("List/availableDate").toDateTime();

        mSettings.remove("List/available");
        mSettings.remove("List/availableDate");

        if (listFileName.isEmpty() || !listFileDate.isValid()) {
            qWarning() << "Error accessing settings";
            return; /* Try again with next check */
        }

        mSettings.setValue("List/installed", listFileName);
        mSettings.setValue("List/installedDate", listFileDate);
        mInstalledList = mListToInstall;
        mListToInstall = CertificateList();
    }
    loadCertificateList();
}

void MainWindow::installCerts() {
    QStringList choices;

    for (int i = 0; i < mCertListWidget->count(); i++) {
        QListWidgetItem *item = mCertListWidget->item(i);
        if (item->checkState() == Qt::Checked) {
            choices << item->data(CertificateItemDelegate::DataRole).value<Certificate>().base64Line();
            continue;
        }
        QString certLine = item->data(CertificateItemDelegate::DataRole).value<Certificate>().base64Line();
        if (certLine.startsWith("I:")) {
            certLine[0] = 'R';
            choices << certLine;
        }
    }

    QProgressDialog *progress = new QProgressDialog(this);
    progress->setWindowModality(Qt::WindowModal);
    progress->setLabelText(tr("Installing certificates..."));
    progress->setCancelButton(0);
    progress->setRange(0,0);
    progress->setMinimumDuration(0);
    progress->show();

    InstallWrapper *instWrap = new InstallWrapper(this,
                                                  mListToInstall.isValid() ?
                                                  mListToInstall.fileName() :
                                                  mInstalledList.fileName(),
                                                  choices);
    /* Clean up object and progress dialog */
    connect(instWrap, SIGNAL(finished()), instWrap, SLOT(deleteLater()));
    connect(instWrap, SIGNAL(finished()), progress, SLOT(deleteLater()));
    connect(instWrap, SIGNAL(finished()), progress, SLOT(cancel()));
    connect(instWrap, SIGNAL(installationSuccessful()),
            this, SLOT(installerSuccess()));
    connect(instWrap, SIGNAL(error(const QString &)),
            this, SLOT(installerError(const QString &)));
    instWrap->start();

    if (!saveUnselectedCertificates()) {
        qWarning() << "Failed to save previosly unselected certificates.";
    }
}

void MainWindow::loadUnselectedCertificates()
{
    mPreviouslyUnselected.clear();
    mSettings.beginGroup("unselected");
    QStringList keys = mSettings.allKeys();
    foreach (const QString &key, keys) {
        mPreviouslyUnselected << mSettings.value(key, QString()).toString();
    }
    mSettings.endGroup();
}

bool MainWindow::saveUnselectedCertificates()
{
    mPreviouslyUnselected.clear();
    mSettings.beginGroup("unselected");
    mSettings.remove(""); /* Clears old choices */
    for (int i = 0; i < mCertListWidget->count(); i++) {
        QListWidgetItem *item = mCertListWidget->item(i);
        if (item->checkState() != Qt::Checked &&
            (item->flags() & Qt::ItemIsUserCheckable)) {
            QString key = QString::fromLatin1("cert%1").arg(i);
            QString value =
                item->data(CertificateItemDelegate::DataRole).value<Certificate>().base64Line();
            mSettings.setValue(key, value);
            mPreviouslyUnselected << value;
        }
    }
    mSettings.endGroup();
    mSettings.sync();
    return mSettings.status() == QSettings::NoError;
}

void MainWindow::saveAutoUpdate(int state)
{
    mSettings.beginGroup("settings");
    mSettings.setValue("autoupdate", state != Qt::Unchecked);
    mSettings.endGroup();
}

void MainWindow::saveAutoStart(int state)
{
    mSettings.beginGroup("settings");
    mSettings.setValue("autostart", state != Qt::Unchecked);
    mSettings.endGroup();
}

void MainWindow::closeApp()
{
    saveUnselectedCertificates();
    qApp->quit();
}

void MainWindow::checkAndInstallCerts()
{
    /* Checking before opening the dialog should be cheaper */
    QList<int> pids = ProcessHelp::getProcessesIdForName("firefox");
    pids.append(ProcessHelp::getProcessesIdForName("thunderbird"));

    if (pids.isEmpty()) {
        installCerts();
        return;
    }

    QStringList pNames;
    pNames << "firefox" << "thunderbird";

    ProcessWaitDialog *waitDialog = new ProcessWaitDialog(this, pNames);

    connect(waitDialog, SIGNAL(accepted()), this, SLOT(installCerts()));
    connect(waitDialog, SIGNAL(accepted()), waitDialog, SLOT(deleteLater()));

    waitDialog->exec();

    return;
}

http://wald.intevation.org/projects/trustbridge/