view ui/mainwindow.cpp @ 1176:c8f698ca6355

(issue128) Rename cinst to trustbridge-certificate-installer
author Andre Heinecke <andre.heinecke@intevation.de>
date Mon, 22 Sep 2014 11:34:06 +0200
parents 2a1206932f53
children 9bdce8d6fd43
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 <QAction>
#include <QDialog>
#include <QDir>
#include <QMenu>
#include <QApplication>
#include <QFile>
#include <QTemporaryDir>
#include <QTimer>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QGridLayout>
#include <QGroupBox>
#include <QSplitter>
#include <QLabel>
#include <QImage>
#include <QCheckBox>
#include <QButtonGroup>
#include <QToolButton>
#include <QStandardPaths>
#include <QDesktopServices>

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

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

#define DATETIME_FORMAT "d. MMM yyyy HH:mm"

#ifndef APPNAME
#define APPNAME "TrustBridge"
#endif

#ifndef SERVER_URL
#error "DOWNLOAD_SERVER option not set or invalid."
#endif

#if defined(_X86_) || defined(__i386__)
#define TB_ARCH_STRING "-i386"
#else
#define TB_ARCH_STRING "-amd64"
#endif

#ifdef IS_TAG_BUILD
# define LIST_RESOURCE "/zertifikatsliste.txt"
# ifdef Q_OS_WIN
#  define SW_RESOURCE_VERSION "/TrustBridge-%1.exe"
#  define SW_RESOURCE "/TrustBridge.exe"
# else
#  define SW_RESOURCE_VERSION "/TrustBridge-%1" TB_ARCH_STRING ".sh"
#  define SW_RESOURCE "/TrustBridge" TB_ARCH_STRING ".sh"
# endif
#else // Not tag build means develpment build
# define LIST_RESOURCE "/zertifikatsliste.txt"
# ifdef Q_OS_WIN
#  define SW_RESOURCE_VERSION "/development/TrustBridge-%1.exe"
#  define SW_RESOURCE "/development/TrustBridge.exe"
# else
#  define SW_RESOURCE_VERSION "/development/TrustBridge-%1" TB_ARCH_STRING ".sh"
#  define SW_RESOURCE "/development/TrustBridge" TB_ARCH_STRING ".sh"
# endif
#endif

/* Help installation path the path relative to the installation directory where
 * the help is placed.*/
#ifdef WIN32
#define HELP_PATH "/doc/index.html"
#else
#define HELP_PATH "/../share/doc/trustbridge/index.html"
#endif

static void activateDetailsButton(QPushButton *);
static void deactivateDetailsButton(QPushButton *);

MainWindow::MainWindow(bool trayMode):
    mTrayMode(trayMode),
    mManualDetailsShown(false)
{
    createActions();
    createTrayIcon();
    setupGUI();
    resize(900, 600);
    setMinimumWidth(760);
    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 (mSettings.value("ShowOnNextStart").toBool()) {
        mSettings.remove("ShowOnNextStart");
        show();
    } else 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 == NewSoftwareAvailable) {
        verifySWData();
        QString swFileName = mSettings.value("Software/available").toString();
        if (swFileName.isEmpty()) {
            checkUpdates(true);
            mCurState = DownloadingSW;
            return;
        }
        installNewSW(swFileName,
                mSettings.value("Software/availableDate").toDateTime());
    } else {
        show();
    }
}

void MainWindow::showMessage()
{
    if (mCurMessage.isEmpty()) {
        return;
    }

    if (!mTrayIcon->isVisible() && !mTrayIcon->isAlternative()) {
        mTrayIcon->show();
        /* When the message is shown before the tray icon is fully visble
         * and becoming visible may be delayed on some desktop environments
         * the message will pop up somehere on the screen and not over
         * the trayicon. So we delay here.*/
        QTimer::singleShot(2000, this, SLOT(showMessage()));
    } else if (mCurState == NewSoftwareAvailable || !isVisible()) {
        if (mCurState == NewListAvailable) {
            mTrayIcon->showMessage(QApplication::applicationName(), mCurMessage,
                                   QSystemTrayIcon::Information, 10000,
                                   tr("Show recommendations"));
        } else if (mCurState == NewSoftwareAvailable || !mTrayIcon->isAlternative()) {
            /* Only show new list or new software in alternative as
             * the current tray icon alternative is too invasive for pure
             * informational messages. */
            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.toUtf8().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.toUtf8().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()) {
        qDebug() << "Date set but no fileName";
        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.result;
    if (verifyResult.result != VerifyValid) {
        qDebug() << "Failed to verify downloaded data.";
        QFile::remove(swFileName);
        mSettings.remove("Software/available");
        mSettings.remove("Software/availableDate");
        return;
    }
    fclose(verifyResult.fptr);
}

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

    verifyListData();
    if (!mListToInstall.isValid()) {
        /* Downloader provided invalid files */
        /* TODO (issue38): 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()));
        qDebug() << "Failed to verify list.";
    } else {
        if (mTrayIcon->isAlternative()) {
            mCurMessage = tr("An updated certificate list is available.");
        } else {
            mCurMessage = tr("An updated certificate list is available.") +" " + tr("Click here to install.");
        }
        setState(NewListAvailable);
        showMessage();
        loadCertificateList();
    }
}

void MainWindow::handleNewSW(const QString& fileName, const QDateTime& modDate) {
    if (mTrayIcon->isAlternative()) {
        mCurMessage = tr("An update for %1 is available.").arg(
                QApplication::applicationName());
    } else {
        mCurMessage = QString(tr("An update for %1 is available.") + "\n" +
                tr("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();
}

QString getPrettyInstallerName(QString realFileName) {
    QTemporaryDir tDir;
    if (!tDir.isValid()) {
        qDebug () << "Failed to create temporary directory.";
        return QString();
    }
    QString targetPath = tDir.path() + "/" + QObject::tr("TrustBridge-Updater",
            "Used as filename for the updater. Only use ASCII please.");

    tDir.setAutoRemove(false);
#ifdef WIN32
    targetPath += ".exe";
#endif
    if (!QFile::copy(realFileName, targetPath)) {
        qDebug() << "Failed to create temporary copy of installer.";
    }
    return targetPath;
}

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

    /* Copy the file to a temporary name for installation */
    filePath = getPrettyInstallerName(filePath);

    if (filePath.isEmpty()) {
        qDebug() << "Failed to copy updater to temporary location.";
        showErrorMessage(tr("Failed to create update process.") + "\n" +
                tr("This could be caused by not enough disk space or invalid permissions."));
        return;
    }
    mSettings.setValue("Software/Updater", filePath); /* So it can be deleted
                                                         on next start */
    mSettings.sync();

    bin_verify_result vres = verify_binary(filePath.toUtf8().constData(),
            filePath.toUtf8().size());

    if (vres.result != 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_system_install()) {
        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);
        fclose(vres.fptr);
        return;
    }
#else /* WIN32 */
    /* On linux installDir is <prefix>/bin */
    QStringList parameters;
    installDir.cdUp();
    parameters << "--prefix" << installDir.path();
    parameters << "--update";
    bool sudo_started = false;
    bool use_sudo = is_admin() && is_system_install();
    if (use_sudo) {
        QStringList sudoPrograms;
        sudoPrograms << "pkexec" << "kdesudo" << "sudo";
        QStringList sudoParams;
        sudoParams << filePath + " " + parameters.join(" ");

        foreach (const QString &sProg, sudoPrograms) {
            qDebug() << "Starting process " << sProg <<" params: " << sudoParams;
            if (!QProcess::startDetached(sProg, sudoParams)) {
                continue;
            } else {
                sudo_started = true;
                break;
            }
        }
    }
    qDebug() << "Starting process " << filePath <<" params: " << parameters;
    if (!sudo_started && !QProcess::startDetached(filePath, parameters)) {
        qDebug() << "Failed to start process.";
        fclose(vres.fptr);
        return;
    }

#endif
    if (isVisible()) {
        mSettings.setValue("ShowOnNextStart", true);
        mSettings.sync();
    }

    syslog_info_printf ("Installing update: %s\n", fileName.toUtf8().constData());
    /* Installer process should now be running. We exit */
    fclose(vres.fptr);
    closeApp();
}

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

    /* Delete old temporary installers if they exist */
    QString oldUpdater = mSettings.value("Software/Updater").toString();

    if (!oldUpdater.isEmpty()) {
        qDebug() << "Removing old updater: " << oldUpdater;
        QFileInfo fiUpdater(oldUpdater);
        if (!QFile::remove(fiUpdater.absoluteFilePath())) {
            qDebug() << "Failed to remove file";
        } else {
            if (!fiUpdater.absoluteDir().rmdir(fiUpdater.absoluteDir().absolutePath())) {
                qDebug() << "Failed to remove temporary directory.";
            }
        }
        mSettings.remove("Software/Updater");
    }

    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();

    QDateTime swAvailableLastMod = mSettings.value("Software/availableDate").toDateTime();

    if (swAvailableLastMod.isValid()) {
        qDebug() << "Installed an update: " << swInstalledLastMod <<
            " available " << swAvailableLastMod;
        syslog_info_printf ("Software has been updated to version: %s\n",
                QApplication::applicationVersion().toUtf8().constData());
        if (swInstalledLastMod == swAvailableLastMod) {
            QString fileName = mSettings.value("Software/available").toString();
            if (fileName.isEmpty()) {
                qDebug() << "Software marked as available but no filename set.";
            } else {
                if (QFile::remove(fileName)) {
                    qDebug() << "Removed: " << fileName;
                } else {
                    qDebug() << "Failed to remove: " << fileName;
                }
            }
            /* Clear out available data. */
            mSettings.remove("Software/available");
            mSettings.remove("Software/availableDate");
        }
    }

    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) {
        setState(BeforeDownload);
        connect(downloader, SIGNAL(newSoftwareAvailable(const QString&, const QDateTime&)),
                this, SLOT(handleNewSW(const QString&, const QDateTime&)));
    } else {
        setState(DownloadingSW);
        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(finished()), this, SLOT(updateCheckSuccess()));
    connect(downloader, SIGNAL(error(const QString &, SSLConnection::ErrorCode)),
            this, SLOT(downloaderError(const QString &, SSLConnection::ErrorCode)));
    downloader->start();

    if (downloadSW && getState() == DownloadingSW && isVisible()) {
        QProgressDialog *progDlg = new QProgressDialog(this);
        progDlg->setCancelButton(0);
        progDlg->setRange(0,0);
        progDlg->setMinimumDuration(0);
        progDlg->setLabelText(tr("Downloading update..."));
        progDlg->show();
        connect(downloader, SIGNAL(finished()), progDlg, SLOT(deleteLater()));
        connect(downloader, SIGNAL(finished()), progDlg, SLOT(cancel()));
    }
}

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&)));
    connect(this, SIGNAL(destroyed(QObject*)), downloader, SLOT(quit()));
    downloader->start();
}

void MainWindow::setLastModifiedSWDate(const QDateTime &date)
{
    mSettings.beginGroup("Software");
#ifdef IS_TAG_BUILD
    /* We accept an invalid date to force installing any avialable update
     * in release mode. Otherwise we default to current datetime when we
     * did not find out version.*/
    mSettings.setValue("installedDate", date);
#else
    mSettings.setValue("installedDate", date.isValid() ? date : QDateTime::currentDateTime());
#endif
    mSettings.setValue("installedVersion", QApplication::applicationVersion());
    mSettings.endGroup();
    checkUpdates();
}

void MainWindow::downloaderError(const QString &message, SSLConnection::ErrorCode error)
{
    /* TODO (issue38) handle error according to a plan */
    syslog_error_printf ("Failed to check for updates: %s", message.toUtf8().constData());
#ifdef IS_TAG_BUILD
    /* During tag build it should never happen that an url checked is not available
     * during development this is normal as each revision produces a new url. */
    setState(TransferError);
    if (!isVisible()) {
        mCurMessage = message;
        mTrayIcon->show();
        showMessage();
    } else {
        showErrorMessage(tr("Failed to check for updates:") + "\n"  + message);
    }
#endif
}

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");
    trayImg.addFile(":/img/tray_48.png", QSize(48,48));

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

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

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

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

QWidget * MainWindow::createInfoWidget()
{
    QWidget *theWidget = new QWidget;
    QVBoxLayout *infoPanelLayout = new QVBoxLayout;
    QHBoxLayout *infoHeaderLayout = new QHBoxLayout;
    QVBoxLayout *infoHeaderTextLayout = new QVBoxLayout;
    QVBoxLayout *infoCenterLayout = new QVBoxLayout;

    QString infoVersion = tr("Version: ");
    infoVersion.append(QApplication::applicationVersion());
    QLabel *appVersion = new QLabel(infoVersion);
    appVersion->setTextInteractionFlags(
        Qt::TextSelectableByMouse |
        Qt::TextSelectableByKeyboard);

    QFrame *infoHeaderSeparator = new QFrame();
    infoHeaderSeparator->setFrameShape(QFrame::HLine);
    infoHeaderSeparator->setFrameShadow(QFrame::Sunken);

    infoHeaderTextLayout->addWidget(appVersion);
    infoHeaderLayout->addLayout(infoHeaderTextLayout);
    infoHeaderLayout->insertStretch(2, 10);

    QLabel *textDesc = new QLabel(tr("TrustBridge is a root certificate"
        " installer for Windows and GNU/Linux.<br/>") +
    tr("The root certificate lists are managed"
        " by the German <a href=\"https://www.bsi.bund.de\">"
        "Federal Office for Information Security (BSI)</a>.<br/><br/>") +
    tr("The software was developed by the companies"
        " <a href=\"http://www.intevation.de\">Intevation GmbH</a> and "
        " <a href=\"http://www.dn-systems.de\">DN-Systems GmbH</a>, <br>"
        " contracted by the German Federal Office for Information Security (BSI).<br/><br/>") +
    tr("TrustBridge is Free Software licensed"
        " under GNU GPL v2+.<br/><br/>Copyright (C) 2014 by Bundesamt für Sicherheit"
        " in der Informationstechnik"));
    textDesc->setTextFormat(Qt::RichText);
    textDesc->setTextInteractionFlags(
        Qt::TextSelectableByMouse |
        Qt::TextSelectableByKeyboard |
        Qt::LinksAccessibleByMouse);
    textDesc->setOpenExternalLinks(true);

    infoCenterLayout->addWidget(infoHeaderSeparator);
    infoCenterLayout->addWidget(textDesc);
    infoCenterLayout->insertSpacing(2, 10);
    infoCenterLayout->insertSpacing(4, 10);
    infoCenterLayout->insertSpacing(6, 10);

    QHBoxLayout *helpButtonLayout = new QHBoxLayout();
    QPushButton *helpButton = new QPushButton(" " + tr("Show Help"));
    helpButton->setIcon(QIcon(":/img/show-help_16.png"));
    connect(helpButton, SIGNAL(clicked()), this, SLOT(showHelp()));
    helpButtonLayout->addWidget(helpButton);
#ifdef USE_CURL
    QPushButton *proxySettingsButton = new QPushButton(" " + tr("Proxy settings"));
    proxySettingsButton->setIcon(QIcon(":/img/preferences-network_16.png"));
    connect(proxySettingsButton, SIGNAL(clicked()), this, SLOT(showProxySettings()));
    helpButtonLayout->addWidget(proxySettingsButton);
#endif
    helpButtonLayout->addStretch();
    infoCenterLayout->addLayout(helpButtonLayout);

    infoCenterLayout->insertStretch(8, 10);

    infoPanelLayout->addLayout(infoHeaderLayout);
    infoPanelLayout->addLayout(infoCenterLayout);
    theWidget->setLayout(infoPanelLayout);

    return theWidget;
}

QWidget * MainWindow::createUpdatesWidget()
{
    QWidget * theWidget = new QWidget;
    QVBoxLayout *updatesMainLayout = new QVBoxLayout;

    /* The header */
    QVBoxLayout *updatesHeaderLayout = new QVBoxLayout;

    QGridLayout *detailsLayout = new QGridLayout;

    /* Header 1: Action buttons and summary*/
    mUpdatesHeader =
        new QLabel("<h2>" + tr("Certificates unchanged")+ "</h2>");
    updatesHeaderLayout->addWidget(mUpdatesHeader);

    QHBoxLayout *updatesHeaderActionButtonLayout = new QHBoxLayout;
    mQuitButton = new QPushButton(" " + tr("Quit without saving"));
    mQuitButton->setIcon(QIcon(":/img/application-exit.png"));
    mQuitButton->setFixedHeight(30);

    mInstallButton = new QPushButton(" " + tr("Install certificates again"));
    mInstallButton->setFixedHeight(30);
#ifdef Q_OS_WIN
    if (is_system_install()) {
        QIcon uacShield = QApplication::style()->standardIcon(QStyle::SP_VistaShield);
        mInstallButton->setIcon(uacShield);
    }
#else
    mInstallButton->setIcon(QIcon(":/img/view-refresh_16px.png"));
#endif
    connect(mQuitButton, SIGNAL(clicked()), this, SLOT(closeApp()));
    connect(mInstallButton, SIGNAL(clicked()), this, SLOT(checkAndInstallCerts()));

    updatesHeaderActionButtonLayout->addWidget(mInstallButton);
    updatesHeaderActionButtonLayout->addWidget(mQuitButton);
    updatesHeaderActionButtonLayout->addStretch(-1);

    updatesHeaderLayout->addLayout(updatesHeaderActionButtonLayout);
    updatesHeaderLayout->addSpacing(20);

    /* The splitter line */
    QFrame *line = new QFrame();
    line->setFrameShape(QFrame::HLine);
    line->setFrameShadow(QFrame::Sunken);
    updatesHeaderLayout->addWidget(line);

    updatesMainLayout->addLayout(updatesHeaderLayout);

    /* Central Header Details and update button. Part of the scroll area */
    QScrollArea *centralScrollArea = new QScrollArea;
    QVBoxLayout *updatesCenterLayout = new QVBoxLayout;
    mUpdatesDetailsHeader = new QLabel(QString());

    QHBoxLayout *updateDateAndSearchButton = new QHBoxLayout;
    mCertListVersion =
        new QLabel(QString());
    mCertListVersionContents = new QLabel(QString());
    const QDateTime lastCheck = mSettings.value("lastUpdateCheck").toDateTime().toLocalTime();
    mLastUpdateCheck = new QLabel(tr("Last update check:"));
    if (lastCheck.isValid()) {
        const QString lastUpdateCheck = QLocale::system().toString(lastCheck, DATETIME_FORMAT);
        mLastUpdateCheckContents = new QLabel(lastUpdateCheck);
    } else {
        mLastUpdateCheckContents = new QLabel(tr("No connection with the updateserver."));
    }
    QPushButton *searchUpdates = new QPushButton(" " + tr("Update"));
    searchUpdates->setFixedHeight(22);
    searchUpdates->setToolTip(tr("Check for Updates"));
    searchUpdates->setStyleSheet("font-size: 10px;");
    searchUpdates->setIcon(QIcon(":/img/update-list.png"));
    connect(searchUpdates, SIGNAL(clicked()), this, SLOT(checkUpdates()));
    updateDateAndSearchButton->addWidget(mLastUpdateCheckContents);
    updateDateAndSearchButton->addWidget(searchUpdates);

    mUpdatesTip =
        new QLabel(QString());
    mUpdatesTip->setWordWrap(true);

    // addWidget(*Widget, row, column, rowspan, colspan)
    updatesCenterLayout->addWidget(mUpdatesDetailsHeader);
    detailsLayout->addWidget(mLastUpdateCheck, 0, 0, 1, 1);
    detailsLayout->addLayout(updateDateAndSearchButton, 0, 1, 1, 1);
    detailsLayout->addWidget(mCertListVersion, 1, 0, 1, 1);
    detailsLayout->addWidget(mCertListVersionContents, 1, 1, 1, 1);
    detailsLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum), 2, 2, 1, 1);
    detailsLayout->setColumnStretch(2, 1);

    updatesCenterLayout->addLayout(detailsLayout);

    updatesCenterLayout->addItem(new QSpacerItem(100, 10));
    updatesCenterLayout->addWidget(mUpdatesTip);

    /* The central panels. */
    QHBoxLayout *updatesNewLayout = new QHBoxLayout;
    QHBoxLayout *updatesRemoveLayout = new QHBoxLayout;
    QHBoxLayout *updatesManualLayout = new QHBoxLayout;
    mUpdatesNewCertificates =
        new QLabel("<h3>" +
            tr("Install new trusted certificates (%1/%2)").arg(0).arg(0) +
            "</h3>");
    mUpdatesDetailsNew = new QPushButton();
    mUpdatesDetailsNew->setText(" " + tr("Details"));
    mUpdatesDetailsNew->setToolTip(tr("Show details"));
    mUpdatesDetailsNew->setStyleSheet("font-size: 10px;");
    mUpdatesDetailsNew->setFixedHeight(22);
    mUpdatesDetailsNew->setIcon(QIcon(":/img/dialog-information_16px.png"));
    connect(mUpdatesDetailsNew,
        SIGNAL(clicked()),
        this,
        SLOT(toggleUpdatesNew()));
    updatesNewLayout->addWidget(mUpdatesNewCertificates);
    updatesNewLayout->addWidget(mUpdatesDetailsNew);
    updatesNewLayout->addStretch(1);
    mUpdatesNew = new CertificateListWidget(this);
    connect(mUpdatesNew, SIGNAL(certListChanged(int)),
        this, SLOT(listChanged(int)));
    mUpdatesNew->hide();

    mUpdatesRemoveCertificates =
        new QLabel("<h3>" +
            tr("Remove revoked certificates (%1/%2)").arg(0).arg(0) +
            "</h3>");
    mUpdatesDetailsRemove = new QPushButton();
    mUpdatesDetailsRemove->setText(" " + tr("Details"));
    mUpdatesDetailsRemove->setToolTip(tr("Show details"));
    mUpdatesDetailsRemove->setStyleSheet("font-size: 10px;");
    mUpdatesDetailsRemove->setFixedHeight(22);
    mUpdatesDetailsRemove->setIcon(QIcon(":/img/dialog-information_16px.png"));
    connect(mUpdatesDetailsRemove,
        SIGNAL(clicked()),
        this,
        SLOT(toggleUpdatesRemove()));
    updatesRemoveLayout->addWidget(mUpdatesRemoveCertificates);
    updatesRemoveLayout->addWidget(mUpdatesDetailsRemove);
    updatesRemoveLayout->addStretch(1);
    mUpdatesRemove = new CertificateListWidget(this);
    connect(mUpdatesRemove, SIGNAL(certListChanged(int)),
        this, SLOT(listChanged(int)));
    mUpdatesRemove->hide();

    mUpdatesManualCertificates = new QLabel(QString());
    mUpdatesDetailsManual = new QPushButton();
    mUpdatesDetailsManual->setText(" " + tr("Details"));
    mUpdatesDetailsManual->setToolTip(tr("Show details"));
    mUpdatesDetailsManual->setStyleSheet("font-size: 10px;");
    mUpdatesDetailsManual->setFixedHeight(22);
    mUpdatesDetailsManual->setIcon(QIcon(":/img/dialog-information_16px.png"));
    connect(mUpdatesDetailsManual,
        SIGNAL(clicked()),
        this,
        SLOT(toggleUpdatesManual()));
    mUpdatesDetailsManual->hide();
    updatesManualLayout->addWidget(mUpdatesManualCertificates);
    updatesManualLayout->addWidget(mUpdatesDetailsManual);
    updatesManualLayout->addStretch(1);
    mUpdatesManual = new CertificateListWidget(this);
    mUpdatesManual->hide();
    connect(mUpdatesManual, SIGNAL(certChanged(bool, const Certificate&)),
        this, SLOT(removeFromManual(bool, const Certificate&)));
    connect(mUpdatesManual, SIGNAL(certListChanged(int)),
        this, SLOT(listChanged(int)));

    updatesNewLayout->setAlignment(Qt::AlignTop);
    updatesRemoveLayout->setAlignment(Qt::AlignTop);
    updatesManualLayout->setAlignment(Qt::AlignTop);
    updatesCenterLayout->addLayout(updatesNewLayout);
    updatesCenterLayout->addWidget(mUpdatesNew);
    updatesCenterLayout->addLayout(updatesRemoveLayout);
    updatesCenterLayout->addWidget(mUpdatesRemove);
    updatesCenterLayout->addSpacing(10);
    updatesCenterLayout->addLayout(updatesManualLayout);
    updatesCenterLayout->addWidget(mUpdatesManual);

    QWidget *dummyWidget = new QWidget;
    dummyWidget->setLayout(updatesCenterLayout);
    centralScrollArea->setWidgetResizable(true);
    centralScrollArea->setWidget(dummyWidget);
    centralScrollArea->setFrameShape(QFrame::NoFrame);
    centralScrollArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    updatesMainLayout->addWidget(centralScrollArea);
    updatesCenterLayout->addSpacerItem(new QSpacerItem(0, 0,
                QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding));
    theWidget->setLayout(updatesMainLayout);
    return theWidget;
}


QWidget *MainWindow::createInstallWidget()
{
    QWidget *theWidget = new QWidget;
    QScrollArea *scrollArea = new QScrollArea;
    QVBoxLayout *installPanelLayout = new QVBoxLayout;
    QVBoxLayout *installHeaderLayout = new QVBoxLayout;
    QVBoxLayout *installCenterLayout = new QVBoxLayout;

    QLabel *installHeaderLabel =
        new QLabel("<h2>" + tr("Trusted certificates") + "</h2>");
    QLabel *installHeaderText = new QLabel(tr("The following list of trusted root"
        " certificates is managed by the BSI. The BSI validates independently the"
        " authenticity, security and actuality of these certificates."));
    installHeaderText->setWordWrap(true);
    installHeaderLayout->addWidget(installHeaderLabel);
    installHeaderLayout->addWidget(installHeaderText);

    QLabel *installCenterText = new QLabel(tr("Please choose the certificates"
        " you want to trust or untrust. TrustBridge will install these certificates for your"
        " secure communication for email and internet."));
    installCenterText->setWordWrap(true);
    installCenterLayout->addWidget(installCenterText);

    installPanelLayout->addLayout(installHeaderLayout);
    installPanelLayout->addLayout(installCenterLayout);

    mInstallList = new CertificateListWidget(this);
    connect(mInstallList, SIGNAL(certChanged(bool, const Certificate&)),
        this, SLOT(toggleInManual(bool, const Certificate&)));

    scrollArea->setWidgetResizable(true);
    scrollArea->setWidget(mInstallList);
    scrollArea->setFrameShape(QFrame::NoFrame);

    installPanelLayout->addWidget(scrollArea);

    theWidget->setLayout(installPanelLayout);

    return theWidget;
}

QWidget *MainWindow::createRemoveWidget()
{
    QWidget * theWidget = new QWidget;
    QScrollArea *scrollArea = new QScrollArea;
    QVBoxLayout *removePanelLayout = new QVBoxLayout;
    QVBoxLayout *removeHeaderLayout = new QVBoxLayout;
    QVBoxLayout *removeCenterLayout = new QVBoxLayout;

    QLabel *removeHeaderLabel =
        new QLabel("<h2>" + tr("Revoked certificates") + "</h2>");
    QLabel *removeHeaderText = new QLabel(tr("Certificates can be corrupted"
        " or stolen and misused in many ways. Therefore the BSI recommends"
        " to remove all revoked certificates from your system."));
    removeHeaderText->setWordWrap(true);
    removeHeaderLayout->addWidget(removeHeaderLabel);
    removeHeaderLayout->addWidget(removeHeaderText);

    QLabel *removeCenterText = new QLabel(tr("The following unsecure certificates were"
        " revoked by the BSI. Already uninstalled certificates cannot be reinstalled."
        " It is recommended that you select all certificates to uninstall if you still"
        " have revoked certificates installed."));
    removeCenterText->setWordWrap(true);
    removeCenterLayout->addWidget(removeCenterText);
    mRemoveList = new CertificateListWidget(this);
    connect(mRemoveList, SIGNAL(certChanged(bool, const Certificate&)),
        this, SLOT(toggleInManual(bool, const Certificate&)));

    removePanelLayout->addLayout(removeHeaderLayout);
    removePanelLayout->addLayout(removeCenterLayout);

    scrollArea->setWidgetResizable(true);
    scrollArea->setWidget(mRemoveList);
    scrollArea->setFrameShape(QFrame::NoFrame);
    removePanelLayout->addWidget(scrollArea);
    theWidget->setLayout(removePanelLayout);

    return theWidget;
}

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

    // Layouts and Container
    QVBoxLayout *mainLayout = new QVBoxLayout;
    QHBoxLayout *headerLayout = new QHBoxLayout;
    QVBoxLayout *headerTextLayout = new QVBoxLayout;
    QHBoxLayout *headerSubtitleLayout = new QHBoxLayout;
    QHBoxLayout *centerLayout = new QHBoxLayout;
    QVBoxLayout *buttonBarLayout = new QVBoxLayout;
    QHBoxLayout *bottomLayout = new QHBoxLayout;
    QHBoxLayout *containerLayout = new QHBoxLayout;

    // 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("<h1>" + QString::fromLatin1(APPNAME) + "</h1>");
    QLabel *subTitle = new QLabel(tr("Trust in your digital communication"));
    QLabel *swVersion = new QLabel(QString::fromLatin1("<i>") +
            tr("Version") + " " + QApplication::applicationVersion() +
            QString::fromLatin1(" </i>"));

    swVersion->setTextInteractionFlags(Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse);
    swVersion->setTextFormat(Qt::RichText);

    headerSubtitleLayout->addWidget(subTitle);
    headerSubtitleLayout->addStretch(-1);
    headerSubtitleLayout->addWidget(swVersion);

    headerTextLayout->addWidget(title);
    headerTextLayout->addLayout(headerSubtitleLayout);
    headerLayout->addWidget(logo);
    headerLayout->addLayout(headerTextLayout);
    headerLayout->setStretch(0, 0);
    headerLayout->setStretch(1, 10);

    /***********************************
     * The Buttonbar on the left side.
     ***********************************/
    mButtonGroup = new QButtonGroup;

    TextOverlayButton *updatesButton = new TextOverlayButton;
    updatesButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
    updatesButton->setBackgroundIcon(":/img/red-circle.png");
    updatesButton->setIcon(QIcon(":/img/view-refresh.png"));
    updatesButton->setIconSize(QSize(48, 48));
    updatesButton->setText(tr("Pending\nchanges"));
    updatesButton->setFixedWidth(120);
    updatesButton->setFixedHeight(90);
    updatesButton->setCheckable(true);
    updatesButton->setChecked(true);

    connect(this, SIGNAL(changesChanged(const QString&)),
            updatesButton, SLOT(setOverlay(const QString&)));

    QToolButton *allInstallButton = new QToolButton;
    allInstallButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
    allInstallButton->setIcon(QIcon(":/img/document-encrypt.png"));
    allInstallButton->setIconSize(QSize(48, 48));
    allInstallButton->setText(tr("Trusted\ncertificates"));
    allInstallButton->setFixedWidth(120);
    allInstallButton->setFixedHeight(90);
    allInstallButton->setCheckable(true);

    QToolButton *allRemoveButton = new QToolButton;
    allRemoveButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
    allRemoveButton->setIcon(QIcon(":/img/dialog-warning.png"));
    allRemoveButton->setIconSize(QSize(48, 48));
    allRemoveButton->setText(tr("Revoked\ncertificates"));
    allRemoveButton->setFixedWidth(120);
    allRemoveButton->setFixedHeight(90);
    allRemoveButton->setCheckable(true);

    QToolButton *infoButton = new QToolButton;
    infoButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
    infoButton->setIcon(QIcon(":/img/dialog-information.png"));
    infoButton->setIconSize(QSize(48, 48));
    infoButton->setText(tr("Information\nand help"));
    infoButton->setFixedWidth(120);
    infoButton->setFixedHeight(90);
    infoButton->setCheckable(true);

    mButtonGroup->addButton(updatesButton);
    mButtonGroup->addButton(allInstallButton);
    mButtonGroup->addButton(allRemoveButton);
    mButtonGroup->addButton(infoButton);
    mButtonGroup->setId(updatesButton, 0);
    mButtonGroup->setId(allInstallButton, 1);
    mButtonGroup->setId(allRemoveButton, 2);
    mButtonGroup->setId(infoButton, 3);

    connect(mButtonGroup, SIGNAL(buttonClicked(int)),
        this, SLOT(togglePages(int)));
    buttonBarLayout->addWidget(updatesButton);
    buttonBarLayout->addWidget(allInstallButton);
    buttonBarLayout->addWidget(allRemoveButton);
    buttonBarLayout->insertStretch(3, 10);
    buttonBarLayout->addWidget(infoButton);

    /* The main pages.*/

    /* The updates page. */
    mUpdatesWidget = createUpdatesWidget();

    /* Install (trusted certs) Page */
    mInstallWidget = createInstallWidget();

    /**********************************
     * Page for certificates to be removed.
     **********************************/
    mRemoveWidget = createRemoveWidget();

    /**********************************
     * The info page.
     **********************************/
    mInfoWidget = createInfoWidget();

    /********************************
     * The main layout for pages.
     ********************************/
    mInstallWidget->hide();
    mRemoveWidget->hide();
    mInfoWidget->hide();
    containerLayout->addWidget(mUpdatesWidget);
    containerLayout->addWidget(mInstallWidget);
    containerLayout->addWidget(mRemoveWidget);
    containerLayout->addWidget(mInfoWidget);

    centerLayout->addLayout(buttonBarLayout);
    centerLayout->addLayout(containerLayout);

    mainLayout->addLayout(headerLayout);
    mainLayout->addLayout(centerLayout);
    mainLayout->addLayout(bottomLayout);
    base->setLayout(mainLayout);
    setCentralWidget(base);
}

void MainWindow::listChanged(int selected)
{
    Q_UNUSED (selected);
    setChangeCount(mUpdatesRemove->selectedCertCount() +
        mUpdatesNew->selectedCertCount() + mUpdatesManual->certificates().size());

    /* Show a different tip in case of manual changes, updates aviailable, updates and manual
     * changes available */
    if (changeCount() && !mUpdatesManual->certificates().size()) {
        mUpdatesTip->setText(
                tr("You should apply the following, recommended changes to your certificate stores:"));
    } else if (changeCount()) {
        mUpdatesTip->setText(
                tr("You can apply the following, changes to your certificate stores:"));
    } else {
        mUpdatesTip->setText(
                tr("There are currently no changes for your certificate stores."));
    }

    if (!changeCount()) {
        /* No changes */
        mQuitButton->setText(" " + tr("Quit"));
        mUpdatesHeader->setText("<h2>" + tr("Certificates unchanged") +
                "</h2>");
        mInstallButton->setText(" " + tr("Install certificates again"));
    } else {
        mQuitButton->setText(" " + tr("Quit without saving"));
        mUpdatesHeader->setText("<h2>" + tr("Pending changes (%1)")
                .arg(changeCount()) +
                "</h2>");
        mInstallButton->setText(" " + tr("Apply changes"));
    }

    if (mUpdatesManual->certificates().size()) {
        mUpdatesDetailsManual->show();
        if (mManualDetailsShown) {
            mUpdatesManual->show();
            deactivateDetailsButton(mUpdatesDetailsManual);
        } else {
            activateDetailsButton(mUpdatesDetailsManual);
        }
    } else {
        mUpdatesDetailsManual->hide();
        mUpdatesManual->hide();
    }
    mUpdatesManualCertificates->setText("<h2>" +
            tr("Manual changes (%1)").arg(mUpdatesManual->certificates().size()) +
            "</h2>");

    if (mUpdatesNew->certificates().size()) {
        mUpdatesNewCertificates->setText("<h3>" +
                tr("Install new trusted certificates (%1/%2)")
                .arg(mUpdatesNew->selectedCertCount())
                .arg(mUpdatesNew->certificates().size()) +
                "</h3>");
        mUpdatesDetailsNew->show();
        mUpdatesDetailsNew->setIcon(QIcon(":/img/dialog-information_16px.png"));
        mUpdatesDetailsNew->setToolTip(tr("Show details"));
        mUpdatesNewCertificates->show();
    } else {
        mUpdatesDetailsNew->hide();
        mUpdatesNew->hide();
        mUpdatesNewCertificates->hide();
    }

    if (mUpdatesRemove->certificates().size()) {
        mUpdatesRemoveCertificates->setText("<h3>" +
                tr("Remove revoked certificates (%1/%2)")
                .arg(mUpdatesRemove->selectedCertCount())
                .arg(mUpdatesRemove->certificates().size()) +
                "</h3>");
        mUpdatesRemoveCertificates->show();
        mUpdatesDetailsRemove->setIcon(QIcon(":/img/dialog-information_16px.png"));
        mUpdatesDetailsRemove->setToolTip(tr("Show details"));
        mUpdatesDetailsRemove->show();
    } else {
        mUpdatesRemoveCertificates->hide();
        mUpdatesDetailsRemove->hide();
        mUpdatesRemove->hide();
    }

    /* Update the details header */
    if (mUpdatesRemove->certificates().size() ||
        mUpdatesNew->certificates().size()) {
        mUpdatesDetailsHeader->setText("<h2>" +
                tr("New, recommended changes (%1/%2)")
                .arg(mUpdatesRemove->selectedCertCount() +
                     mUpdatesNew->selectedCertCount())
                .arg(mUpdatesRemove->certificates().size() +
                     mUpdatesNew->certificates().size()) +
                "</h2>");
    } else {
        mUpdatesDetailsHeader->setText(QString::fromLatin1("<h2>") +
            tr("No new recommendations") + QString::fromLatin1("</h2>"));
    }

    if (mListToInstall.isValid()) {
        mCertListVersion->setText(tr("Certificate list from:"));
        mCertListVersionContents->setText(QLocale::system().toString(
                mListToInstall.date().toLocalTime(), DATETIME_FORMAT));
    } else {
        if (mInstalledList.isValid()) {
            mCertListVersion->setText(tr("Currently installed certificate list:"));
            mCertListVersionContents->setText(QLocale::system().toString(
                    mInstalledList.date().toLocalTime(), DATETIME_FORMAT));
        } else {
            mCertListVersion->setText(tr("No certificate list installed."));
            mCertListVersionContents->setText("");
        }
    }
}

void MainWindow::loadCertificateList()
{
    /* TODO (issue134): if nothing is available (neither old nor new) add some progress
     * indication */
    mInstallList->clear();
    mRemoveList->clear();
    mUpdatesNew->clear();
    mUpdatesRemove->clear();
    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()) {
            bool state = !mPreviouslyUnselected.contains(cert.base64Line());
            if (cert.isInstallCert()) {
                oldInstallCerts.append(cert);
                QToolButton* actionBtn = new QToolButton();
                QIcon btnIcon;
                if (!state) {
                    btnIcon.addFile(":/img/write-into-48.png", QSize(48, 48), QIcon::Normal, QIcon::Off);
                    btnIcon.addFile(":/img/cert-not-installed-bad-48.png", QSize(48, 48), QIcon::Normal, QIcon::On);
                    actionBtn->setProperty("ToolTip_Off", tr("Certificate will be installed."));
                    actionBtn->setProperty("ToolTip_On", tr("Certifcate is not installed."));
                } else {
                    actionBtn->setProperty("ToolTip_Off", tr("Certificate is installed."));
                    actionBtn->setProperty("ToolTip_On", tr("Certificate will be removed."));
                    btnIcon.addFile(":/img/cert-is-installed-good-48.png", QSize(48, 48), QIcon::Normal, QIcon::Off);
                    btnIcon.addFile(":/img/write-remove-48.png", QSize(48, 48), QIcon::Normal, QIcon::On);
                }
                actionBtn->setIcon(btnIcon);
                mInstallList->addCertificate(cert, state, actionBtn);
            }
            else {
                oldRemoveCerts.append(cert);
                QToolButton* actionBtn = new QToolButton();
                QIcon btnIcon;
                actionBtn->setProperty("ToolTip_Off", tr("Certificate will be removed."));
                actionBtn->setProperty("ToolTip_On", tr("Certificate has not been removed."));
                btnIcon.addFile(":/img/write-remove-48.png", QSize(48, 48), QIcon::Normal, QIcon::Off);
                btnIcon.addFile(":/img/cert-is-installed-bad-48.png", QSize(48, 48), QIcon::Normal, QIcon::On);
                btnIcon.addFile(":/img/cert-not-installed-good-48.png", QSize(48, 48), QIcon::Disabled, QIcon::Off);
                actionBtn->setIcon(btnIcon);
                if (state) {
                    actionBtn->setEnabled(false);
                    actionBtn->setToolTip(tr("Certificate has been removed."));
                }
                mRemoveList->addCertificate(cert, state, actionBtn);
            }
        }
    }
    else {
        // Sort and filter both lists.
        foreach (const Certificate &cert, mListToInstall.getCertificates()) {
            bool state = !mPreviouslyUnselected.contains(cert.base64Line());
            if (cert.isInstallCert()) {
                // Certificate with status "install".
                if (mInstalledList.getCertificates().contains(cert)) {
                    // Was in the old list.
                    oldInstallCerts.append(cert);
                    QToolButton* actionBtn = new QToolButton();
                    QIcon btnIcon;
                    actionBtn->setProperty("ToolTip_Off", tr("Certificate is installed."));
                    actionBtn->setProperty("ToolTip_On", tr("Certifcate is not installed."));
                    btnIcon.addFile(":/img/cert-is-installed-good-48.png", QSize(48, 48), QIcon::Normal, QIcon::Off);
                    btnIcon.addFile(":/img/cert-not-installed-bad-48.png", QSize(48, 48), QIcon::Normal, QIcon::On);
                    actionBtn->setIcon(btnIcon);
                    mInstallList->addCertificate(cert, state, actionBtn);
                }
                else {
                    // Is a brand new certificate
                    newInstallCerts.append(cert);
                    QToolButton* actionBtn = new QToolButton();
                    QIcon btnIcon;
                    actionBtn->setProperty("ToolTip_Off", tr("Certificate will be installed."));
                    actionBtn->setProperty("ToolTip_On", tr("Certificate will not be installed."));
                    btnIcon.addFile(":/img/write-into-48.png", QSize(48, 48), QIcon::Normal, QIcon::Off);
                    btnIcon.addFile(":/img/cert-not-installed-bad-48.png", QSize(48, 48), QIcon::Normal, QIcon::On);
                    actionBtn->setIcon(btnIcon);
                    mUpdatesNew->addCertificate(cert, state, actionBtn);
                }
            }
            else {
                // Certificate with status "remove".
                if (mInstalledList.getCertificates().contains(cert)) {
                    // Was in the old list.
                    oldRemoveCerts.append(cert);
                    // Is removed, so set editable to false.
                    QToolButton* actionBtn = new QToolButton();
                    QIcon btnIcon;
                    actionBtn->setProperty("ToolTip_Off", tr("Certificate will be removed."));
                    actionBtn->setProperty("ToolTip_On", tr("Certificate has not been removed."));
                    btnIcon.addFile(":/img/write-remove-48.png", QSize(48, 48), QIcon::Normal, QIcon::Off);
                    btnIcon.addFile(":/img/cert-is-installed-bad-48.png", QSize(48, 48), QIcon::Normal, QIcon::On);
                    btnIcon.addFile(":/img/cert-not-installed-good-48.png", QSize(48, 48), QIcon::Disabled, QIcon::Off);
                    actionBtn->setIcon(btnIcon);
                    if (state) {
                        actionBtn->setEnabled(false);
                        actionBtn->setToolTip(tr("Certificate has been removed."));
                    }
                    mRemoveList->addCertificate(cert, state, actionBtn);
                }
                else {
                    // Was in the old list with status "install" and now has the
                    // status "remove".
                    newRemoveCerts.append(cert);
                    QToolButton* actionBtn = new QToolButton();
                    QIcon btnIcon;
                    actionBtn->setProperty("ToolTip_Off", tr("Certificate will be removed."));
                    actionBtn->setProperty("ToolTip_On", tr("Certificate will not be removed."));
                    btnIcon.addFile(":/img/write-remove-48.png", QSize(48, 48), QIcon::Normal, QIcon::Off);
                    btnIcon.addFile(":/img/cert-is-installed-bad-48.png", QSize(48, 48), QIcon::Normal, QIcon::On);
                    actionBtn->setIcon(btnIcon);
                    mUpdatesRemove->addCertificate(cert, state, actionBtn);
                }
            }
        }
    }

    listChanged(0);
}

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();
    if (!listFileName.isEmpty() && listFileDate.isValid()) {
        mSettings.remove("List/available");
        mSettings.remove("List/availableDate");

        /* Rename the installed list to list-installed.txt so that external
         * programs (like the uninstaller can easily recognize it). */
        QString dataLoc =
            QStandardPaths::writableLocation(QStandardPaths::DataLocation);
        QDir dataDir(dataLoc);
        if (!dataDir.exists()) {
            /* Should not happen */
            qWarning() << "Data dir removed.";
            return;
        }

        QFileInfo oldList (dataDir.absoluteFilePath("list-installed.txt"));
        if (oldList.exists()) {
            qDebug() << "Removing old list: " << oldList.filePath();
            if (!QFile::remove(oldList.filePath())) {
                qWarning() << "Removal of old list failed.";
                return;
            }
        }
        QFile newList(listFileName);
        if (!newList.rename(oldList.filePath())) {
            qWarning() << "Failed to rename new list.";
            return;
        }

        mSettings.setValue("List/installed", oldList.filePath());
        mSettings.setValue("List/installedDate", listFileDate);
        mInstalledList = CertificateList(oldList.filePath().toUtf8().constData());
        if (!mInstalledList.isValid()) {
            /* Something went wrong. Go back to square one. */
            qWarning () << "List corrupted after installation";
            mInstalledList = CertificateList();
            QFile::remove(oldList.filePath());
            mSettings.remove("List/installed");
            mSettings.remove("List/installedDate");
        }
    }
    mListToInstall = CertificateList();
    mUpdatesManual->clear();
    loadCertificateList();
}

void MainWindow::installCerts() {
    QStringList choices;
    QStringList unselected;

    choices << mUpdatesNew->selectedCertificates();
    choices << mUpdatesRemove->selectedCertificates();

    choices << mUpdatesManual->selectedCertificates();

    /* Also include the old certificates */
    choices << mInstallList->selectedCertificates();
    choices << mRemoveList->selectedCertificates();

    QStringList unselectedManuals = mUpdatesManual->unselectedCertificates();
    for(int i = 0; i < unselectedManuals.size(); i++) {
        if (unselectedManuals.at(i).startsWith("I:")) {
            QString certLine = unselectedManuals.at(i);
            certLine[0] = 'R';
            choices << certLine;
        }
    }

    unselected << mUpdatesNew->unselectedCertificates();
    unselected << mUpdatesRemove->unselectedCertificates();
    unselected << mInstallList->unselectedCertificates();
    unselected << mRemoveList->unselectedCertificates();

#ifdef Q_OS_WIN
    if (!is_system_install()) {
        QMessageBox::warning(this,
                tr("Installation with standard user account"),
	        tr("Windows will now ask you to confirm each root certificate modification "
		   "because TrustBridge does not have the necessary privileges to install "
		   "root certificates into the Windows certificate store silently."));
    }
#endif

    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();

    CertificateList *instList = mListToInstall.isValid() ?
                                &mListToInstall :
                                &mInstalledList;

    InstallWrapper *instWrap = new InstallWrapper(this,
                                                  instList->fileName(),
                                                  choices);

    syslog_info_printf ("Installing certificate list: '%s' Version '%s'\n",
            instList->fileName().toUtf8().constData(),
            instList->date().toString().toUtf8().constData());
    /* 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(unselected)) {
        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(QStringList unselected)
{
    mPreviouslyUnselected.clear();
    mSettings.beginGroup("unselected");
    mSettings.remove(""); /* Clears old choices */
    for (int i = 0; i < unselected.size(); i++) {
        QString key = QString::fromLatin1("cert%1").arg(i);
        QString value = unselected.at(i);
        mSettings.setValue(key, value);
        mPreviouslyUnselected << value;
    }
    mSettings.endGroup();
    mSettings.sync();
    return mSettings.status() == QSettings::NoError;
}

void MainWindow::toggleInManual(bool state, const Certificate &cert)
{
    if (!mUpdatesManual->contains(cert)) {
        QToolButton* actionBtn = new QToolButton();
        QIcon btnIcon;
        btnIcon.addFile(":/img/write-into-48.png", QSize(48, 48), QIcon::Normal, QIcon::On);
        btnIcon.addFile(":/img/write-remove-48.png", QSize(48, 48), QIcon::Normal, QIcon::Off);
        actionBtn->setProperty("ToolTip_On", tr("Certificate will be installed."));
        actionBtn->setProperty("ToolTip_Off", tr("Certificate will be removed."));
        actionBtn->setIcon(btnIcon);
        mUpdatesManual->addCertificate(cert, state, actionBtn);
    }
    else {
        mUpdatesManual->removeCertificate(cert);
    }
}

void MainWindow::removeFromManual(bool state, const Certificate &cert)
{
    mUpdatesManual->removeCertificate(cert);

    if (cert.isInstallCert()) {
        mInstallList->setCertState(state, cert);
    }
    else {
        mRemoveList->setCertState(state, cert);
    }
}

void MainWindow::closeApp()
{
    ProcessHelp::cleanUp();
    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;
}

void MainWindow::togglePages(int button)
{
    mUpdatesWidget->hide();
    mInstallWidget->hide();
    mRemoveWidget->hide();
    mInfoWidget->hide();
    switch(button) {
    case 0: mUpdatesWidget->show(); break;
    case 1: mInstallWidget->show(); break;
    case 2: mRemoveWidget->show(); break;
    case 3: mInfoWidget->show(); break;
    default: mUpdatesWidget->show(); break;
    }
    return;
}

static void deactivateDetailsButton(QPushButton *btn) {
    btn->setToolTip(QObject::tr("Hide details"));
    btn->setText(" " + QObject::tr("Less"));
    btn->setIcon(QIcon(":/img/dialog-information_grey_16px.png"));
}

static void activateDetailsButton(QPushButton *btn) {
    btn->setToolTip(QObject::tr("Show details"));
    btn->setText(" " + QObject::tr("Details"));
    btn->setIcon(QIcon(":/img/dialog-information_16px.png"));
}

void MainWindow::toggleUpdatesNew() {
    if (!mUpdatesNew->isVisible()) {
        mUpdatesNew->show();
        deactivateDetailsButton(mUpdatesDetailsNew);
    }
    else {
        mUpdatesNew->hide();
        activateDetailsButton(mUpdatesDetailsNew);
    }
}

void MainWindow::toggleUpdatesRemove() {
    if (!mUpdatesRemove->isVisible()) {
        mUpdatesRemove->show();
        deactivateDetailsButton(mUpdatesDetailsRemove);
    }
    else {
        mUpdatesRemove->hide();
        activateDetailsButton(mUpdatesDetailsRemove);
    }
}

void MainWindow::toggleUpdatesManual() {
    if (!mUpdatesManual->isVisible()) {
        mUpdatesManual->show();
        mManualDetailsShown = true;
        deactivateDetailsButton(mUpdatesDetailsManual);
    }
    else {
        mUpdatesManual->hide();
        mManualDetailsShown = false;
        activateDetailsButton(mUpdatesDetailsManual);
    }
}

void MainWindow::closeEvent(QCloseEvent *event)
{
    if (getState() == NewListAvailable) {
        /* Only minimize to tray if there is a new list */
        QMainWindow::closeEvent(event);
        mTrayIcon->show();
        return;
    }
    return closeApp();
}

void MainWindow::updateCheckSuccess()
{
    if (getState() != TransferError) {
        const QDateTime now = QDateTime::currentDateTime();
        mSettings.setValue("lastUpdateCheck", now);
        mLastUpdateCheckContents->setText(QLocale::system().toString(now, DATETIME_FORMAT));
        mLastUpdateCheckContents->show();
        mLastUpdateCheck->show();
        syslog_info_printf(tr("Sucessfully checked for updates.").toUtf8().constData());
    }
    if ((getState() != NewSoftwareAvailable && getState() != NewListAvailable && mTrayMode)
            && !isVisible()) {
        qDebug() << "Shutting down as no list or Software is available.";
        closeApp();
    } else {
        mTrayIcon->show();
    }
}

int MainWindow::changeCount()
{
    return mChangeCount;
}

void MainWindow::setChangeCount(int cnt)
{
    if (mChangeCount != cnt) {
        mChangeCount = cnt;
        emit changesChanged(QString("%1").arg(cnt));
    }
}

void MainWindow::showProxySettings()
{
    ProxySettingsDlg *dlg = new ProxySettingsDlg(this);
    dlg->exec();
}

void MainWindow::showHelp()
{
    char *inst_dir = get_install_dir();
    if (!inst_dir) {
        qDebug() << "Failed to find install dir";
        return;
    }
    QString helpPath = QString::fromUtf8(inst_dir);
    helpPath += HELP_PATH;
    QFileInfo fiHelp(helpPath);
    qDebug() << "Opening help: " << fiHelp.absoluteFilePath();
    if (!fiHelp.exists()) {
        QMessageBox::warning(this, tr("Error!"), tr ("Failed to find the manual"));
        return;
    }
#ifdef Q_OS_WIN
    QDesktopServices::openUrl(QUrl("file:///" + fiHelp.absoluteFilePath()));
#else
    QDesktopServices::openUrl(QUrl(fiHelp.absoluteFilePath()));
#endif
    free (inst_dir);
    return;
}

void MainWindow::showErrorMessage(const QString &msg)
{
    QMessageBox::warning(this, tr("TrustBridge error"), msg);
}

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