view ui/mainwindow.cpp @ 1070:f110a3f6e387

(issue114) Fine tune ACL propagation using mkdir_p the ACL of the parent directories would propagate to all subdirectories and objects in the directory. Now we only use ACL propagation in the last directory to make sure that files we might create in that directory inherit the correct (resitricted) ACL
author Andre Heinecke <andre.heinecke@intevation.de>
date Wed, 10 Sep 2014 16:41:36 +0200
parents 51b97ebc5b06
children edbf5e5e88f4
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 <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 "dddd, d. MMMM yyyy HH:mm:ss"

#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 RELEASE_BUILD
# define LIST_RESOURCE "/users/aheinecke/zertifikatsliste.txt"
# ifdef Q_OS_WIN
#  define SW_RESOURCE_VERSION "/users/aheinecke/TrustBridge-%1.exe"
#  define SW_RESOURCE "/users/aheinecke/TrustBridge.exe"
# else
#  define SW_RESOURCE_VERSION "/users/aheinecke/TrustBridge-%1" TB_ARCH_STRING ".sh"
#  define SW_RESOURCE "/users/aheinecke/TrustBridge" TB_ARCH_STRING ".sh"
# endif
#else // RELEASE_BUILD
# define LIST_RESOURCE "/users/aheinecke/development/zertifikatsliste.txt"
# ifdef Q_OS_WIN
#  define SW_RESOURCE_VERSION "/users/aheinecke/development/TrustBridge-development.exe"
#  define SW_RESOURCE "/users/aheinecke/development/TrustBridge.exe"
# else
#  define SW_RESOURCE_VERSION "/users/aheinecke/development/TrustBridge-development" TB_ARCH_STRING ".sh"
#  define SW_RESOURCE "/users/aheinecke/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

MainWindow::MainWindow(bool trayMode):
    mTrayMode(trayMode)
{
    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;
    if (verifyResult != VerifyValid) {
        qDebug() << "Failed to verify downloaded data.";
        QFile::remove(swFileName);
        mSettings.remove("Software/available");
        mSettings.remove("Software/availableDate");
        return;
    }
}

void MainWindow::handleNewList(const QString& fileName, const QDateTime& modDate) {
    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();
}

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

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

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

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

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

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

    if (!is_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);
        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.";
        return;
    }

#endif
    /* Installer process should now be running. We exit */

    closeApp();
}

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

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

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

    if (swAvailableLastMod.isValid()) {
        qDebug() << "Installed an update: " << swInstalledLastMod <<
            " available " << swAvailableLastMod;
        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 */
    QGridLayout *updatesHeaderLayout = new QGridLayout;

    /* Header 1: Update date and search button */
    QHBoxLayout *updateDateAndSearchButton = new QHBoxLayout;
    mUpdatesHeader =
        new QLabel("<h2>" + tr("Certificates unchanged")+ "</h2>");
    mLastCertUpdate =
        new QLabel(tr("Installed certificates from:"));
    mLastCertUpdateContents = new QLabel(QString());
    mLastCertUpdate->hide();
    mLastCertUpdateContents->hide();
    mSoftwareVersionLabel =
        new QLabel(tr("TrustBridge Version:"));
    mSoftwareVersionContents = new QLabel(QApplication::applicationVersion());

    const QDateTime lastCheck = mSettings.value("lastUpdateCheck").toDateTime().toLocalTime();
    if (lastCheck.isValid()) {
        const QString lastUpdateCheck = QLocale::system().toString(lastCheck, DATETIME_FORMAT);
        mLastUpdateCheck =
            new QLabel(tr("Last successful update check:"));
        mLastUpdateCheckContents = new QLabel(lastUpdateCheck);
    } else {
        mLastUpdateCheck = new QLabel(tr("Last successful update check:"));
        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);

    // addWidget(*Widget, row, column, rowspan, colspan, [Qt::Alignment])
    updatesHeaderLayout->addWidget(mUpdatesHeader, 0, 0, 1, 2);
    updatesHeaderLayout->addWidget(mSoftwareVersionLabel, 1, 0, 1, 1);
    updatesHeaderLayout->addWidget(mSoftwareVersionContents, 1, 1, 1, 1);
    updatesHeaderLayout->addWidget(mLastUpdateCheck, 2, 0, 1, 1);
    updatesHeaderLayout->addLayout(updateDateAndSearchButton, 2, 1, 1, 1);
//    updatesHeaderLayout->addWidget(mLastUpdateCheckContents, 2, 1, 1, 1);
    updatesHeaderLayout->addWidget(mLastCertUpdate, 3, 0, 1, 1);
    updatesHeaderLayout->addWidget(mLastCertUpdateContents, 3, 1, 1, 1);
    updatesHeaderLayout->setColumnStretch(3, 1);

//    updatesHeaderLayout->addWidget(searchUpdates, 1, 4, 1, 2, Qt::AlignRight);
    updatesHeaderLayout->setRowMinimumHeight(4, 15);

    /* Header 2: Action text and buttons */
    mUpdatesTip =
        new QLabel(tr("There are currently no changes for your certificate stores."));
    mUpdatesTip->setWordWrap(true);
    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()));

    // addWidget(*Widget, row, column, rowspan, colspan)
    updatesHeaderLayout->addWidget(mUpdatesTip, 5, 0, 1, 4);
    updatesHeaderActionButtonLayout->addWidget(mInstallButton);
    updatesHeaderActionButtonLayout->addWidget(mQuitButton);
    updatesHeaderLayout->addLayout(updatesHeaderActionButtonLayout, 6, 0, 1, -1, Qt::AlignRight);
    updatesHeaderLayout->setRowMinimumHeight(7, 10);

    /* The central panels. */
    QScrollArea *centralScrollArea = new QScrollArea;
    QVBoxLayout *updatesCenterLayout = new QVBoxLayout;
    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("<h3>" +
            tr("Manually changed certificates (%1)").arg(0) +
            "</h3>");
    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();
    mUpdatesManualCertificates->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->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->addLayout(updatesHeaderLayout);
    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 *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"));
    headerTextLayout->addWidget(title);
    headerTextLayout->addWidget(subTitle);
    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("Updates"));
    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("Changes to certificate stores (%1)")
                .arg(changeCount()) +
                "</h2>");
        mInstallButton->setText(" " + tr("Apply changes"));
    }

    if (mUpdatesManual->certificates().size()) {
        mUpdatesDetailsManual->show();
        mUpdatesDetailsManual->setIcon(QIcon(":/img/dialog-information_16px.png"));
        mUpdatesDetailsManual->setToolTip(tr("Show details"));
        mUpdatesManualCertificates->show();
    } else {
        mUpdatesDetailsManual->hide();
        mUpdatesManualCertificates->hide();
        mUpdatesManual->hide();
    }

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

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);
                mInstallList->addCertificate(cert, state);
            }
            else {
                oldRemoveCerts.append(cert);
                mRemoveList->addCertificate(cert, state, !state);
            }
        }
    }
    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);
                    mInstallList->addCertificate(cert, state);
                }
                else {
                    // Is a brand new certificate
                    newInstallCerts.append(cert);
                    mUpdatesNew->addCertificate(cert, state, true,
                            tr("install"), tr("ignore"));
                }
            }
            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.
                    mRemoveList->addCertificate(cert, state, !state);
                }
                else {
                    // Was in the old list with status "install" and now has the
                    // status "remove".
                    newRemoveCerts.append(cert);
                    mUpdatesRemove->addCertificate(cert, state);
                }
            }
        }
    }

    if (mInstalledList.date().isValid()) {
        mLastCertUpdateContents->setText(QLocale::system().toString(mInstalledList.date().toLocalTime(),
                        DATETIME_FORMAT));
        mLastCertUpdate->show();
        mLastCertUpdateContents->show();
    }
    mUpdatesManualCertificates->setText("<h3>" +
            tr("Manually changed certificates (%1)").arg(0) +
            "</h3>");
    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();

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

    if (!saveUnselectedCertificates(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)) {
        mUpdatesManual->addCertificate(cert, state);
    }
    else {
        mUpdatesManual->removeCertificate(cert);
    }

    mUpdatesManualCertificates->setText("<h3>" +
            tr("Manually changed certificates (%1)")
                .arg(mUpdatesManual->certificates().size()) +
            "</h3>");
}

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

    mUpdatesManualCertificates->setText("<h3>" +
            tr("Manually changed certificates (%1)")
                .arg(mUpdatesManual->certificates().size()) +
            "</h3>");

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

void MainWindow::toggleUpdatesNew() {
    if (!mUpdatesNew->isVisible()) {
        mUpdatesDetailsNew->setToolTip(tr("Hide details"));
        mUpdatesDetailsNew->setIcon(QIcon(":/img/dialog-information_grey_16px.png"));
        mUpdatesNew->show();
    }
    else {
        mUpdatesNew->hide();
        mUpdatesDetailsNew->setToolTip(tr("Show details"));
        mUpdatesDetailsNew->setIcon(QIcon(":/img/dialog-information_16px.png"));
    }
}

void MainWindow::toggleUpdatesRemove() {
    if (!mUpdatesRemove->isVisible()) {
        mUpdatesDetailsRemove->setToolTip(tr("Hide details"));
        mUpdatesDetailsRemove->setIcon(QIcon(":/img/dialog-information_grey_16px.png"));
        mUpdatesRemove->show();
    }
    else {
        mUpdatesRemove->hide();
        mUpdatesDetailsRemove->setToolTip(tr("Show details"));
        mUpdatesDetailsRemove->setIcon(QIcon(":/img/dialog-information_16px.png"));
    }
}

void MainWindow::toggleUpdatesManual() {
    if (!mUpdatesManual->isVisible()) {
        mUpdatesDetailsManual->setToolTip(tr("Hide details"));
        mUpdatesDetailsManual->setIcon(QIcon(":/img/dialog-information_grey_16px.png"));
        mUpdatesManual->show();
    }
    else {
        mUpdatesDetailsManual->setToolTip(tr("Show details"));
        mUpdatesDetailsManual->setIcon(QIcon(":/img/dialog-information_16px.png"));
        mUpdatesManual->hide();
    }
}

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/