view ui/mainwindow.cpp @ 633:6c090638b2b4

Use static buffer for module file name. According to the msdn examle the return value of getmodulefilename should be used to indicate success and not the size. And according to comments on that function on Windows 8.1 it does not return the needed size. So better be more robust and just use max_path as a limit.
author Andre Heinecke <andre.heinecke@intevation.de>
date Mon, 23 Jun 2014 15:29:48 +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/