view ui/mainwindow.cpp @ 1119:5349e2354c48

(issue54) Merge branch runafterinstall There is now an NSIS Plugin that executes the Software after installation using COM in the shell of the current user. With the way over the shell there is no inheritance / token management required. As it is impossible to drop all privileges of a token granted by UAC and still be able to reelevate the Token again with another RunAs call later this round trip over the Shell was necessary.
author Andre Heinecke <andre.heinecke@intevation.de>
date Tue, 16 Sep 2014 19:48:22 +0200
parents df2297e741ad
children dd9094d92899
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 USE_REAL_RESOURCES
# 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 // RELEASE_BUILD
# define LIST_RESOURCE "/zertifikatsliste.txt"
# ifdef Q_OS_WIN
#  define SW_RESOURCE_VERSION "/development/TrustBridge-development.exe"
#  define SW_RESOURCE "/development/TrustBridge.exe"
# else
#  define SW_RESOURCE_VERSION "/development/TrustBridge-development" 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 (!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) {
        hide();
        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()) {
        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.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: 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 {
    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";
    if (isVisible()) {
        parameters << "--show-after-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

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

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 */
    syslog_error_printf ("Failed to check for updates: %s", message.toUtf8().constData());
    if (!isVisible()) {
        mCurMessage = message;
        mTrayIcon->show();
        showMessage();
    } else {
        showErrorMessage(tr("Failed to check for updates:") + "\n"  + message);
    }
    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");
    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: 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/security-low.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/security-high.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/security-medium.png", QSize(48, 48), QIcon::Normal, QIcon::On);
                btnIcon.addFile(":/img/trash-empty.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/security-high.png", QSize(48, 48), QIcon::Normal, QIcon::Off);
                    btnIcon.addFile(":/img/security-low.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/security-low.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/security-medium.png", QSize(48, 48), QIcon::Normal, QIcon::On);
                    btnIcon.addFile(":/img/trash-empty.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/security-medium.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/