view ui/mainwindow.cpp @ 642:dd738ef17715 trustbridge-refactor

Update the 'remove untrusted' header on list selection change.
author Raimund Renkert <rrenkert@intevation.de>
date Tue, 24 Jun 2014 17:42:57 +0200
parents a7c3ab273d41
children f8be956c819b
line wrap: on
line source
/* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU GPL (v>=2)
 * and comes with ABSOLUTELY NO WARRANTY!
 * See LICENSE.txt for details.
 */
#include "mainwindow.h"

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

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

#ifndef APPNAME
#define APPNAME "TrustBridge"
#endif

#define SERVER_URL "https://files.intevation.de:443"
#define LIST_RESOURCE "/users/aheinecke/zertifikatsliste.txt"
#define SW_RESOURCE   "/users/aheinecke/TrustBridge.exe"
#ifdef Q_OS_WIN
#define SW_RESOURCE_VERSION "/users/aheinecke/TrustBridge-%1.exe"
#else
#define SW_RESOURCE_VERSION "/users/aheinecke/TrustBridge-%1.sh"
#endif

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

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

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

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

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

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

    if (mCurState == NewSoftwareAvailable) {
        checkUpdates(true);
        mCurState = DownloadingSW;
    }
}

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

void MainWindow::verifyAvailableData()
{
    QString availableFileName = mSettings.value("List/available").toString();
    QString installedFileName = mSettings.value("List/installed").toString();
    QString swFileName = mSettings.value("Software/available").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");
    }

    if (!swFileName.isEmpty()) {
        // TODO Verify integrity of the software
    } else {
        mSettings.remove("Software/available");
        mSettings.remove("Software/availableDate");
    }
}

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

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

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

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

    mSettings.sync();
    showMessage();
}

void MainWindow::installNewSW(const QString& fileName, const QDateTime& modDate) {
    QFileInfo instProcInfo = QFileInfo(fileName);
    if (!instProcInfo.isExecutable()) {
        qWarning() << "Downloaded file: " << fileName << " is not executable.";
        setState(TransferError);
        return;
    }
    QString filePath = QDir::toNativeSeparators(instProcInfo.absoluteFilePath());
#ifdef WIN32
    SHELLEXECUTEINFOW shExecInfo;
    memset (&shExecInfo, 0, sizeof(SHELLEXECUTEINFOW));
    shExecInfo.cbSize = sizeof(SHELLEXECUTEINFOW);

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

    shExecInfo.fMask = SEE_MASK_NOASYNC;

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

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

    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 */
    qDebug() << "Starting process " << filePath;

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

    closeApp();
}

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

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

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

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


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

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

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

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

    downloader->start();
}

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

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

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

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

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

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

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

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

void MainWindow::createContent()
{
    // 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;
    QVBoxLayout *updatesPanelLayout = new QVBoxLayout;

    // 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("This Software installs and removes Certificates");
    headerTextLayout->addWidget(title);
    headerTextLayout->addWidget(subTitle);
    headerLayout->addWidget(logo);
    headerLayout->addLayout(headerTextLayout);
    headerLayout->setStretch(0, 0);
    headerLayout->setStretch(1, 10);

    // Buttonbar
    mButtonGroup = new QButtonGroup;

    QToolButton *updatesButton = new QToolButton;
    updatesButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
    updatesButton->setIcon(QIcon(":/img/view-refresh.png"));
    updatesButton->setIconSize(QSize(32, 32));
    updatesButton->setText(tr("Updates"));
    updatesButton->setFixedWidth(90);
    updatesButton->setFixedHeight(70);
    updatesButton->setCheckable(true);
    updatesButton->setChecked(true);

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

    QToolButton *allRemoveButton = new QToolButton;
    allRemoveButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
    allRemoveButton->setIcon(QIcon(":/img/task-attempt.png"));
    allRemoveButton->setIconSize(QSize(32, 32));
    allRemoveButton->setText(tr("Insecure\nCertificates"));
    allRemoveButton->setFixedWidth(90);
    allRemoveButton->setFixedHeight(70);
    allRemoveButton->setCheckable(true);

    QToolButton *infoButton = new QToolButton;
    infoButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
    infoButton->setIcon(QIcon(":/img/dialog-information.png"));
    infoButton->setIconSize(QSize(32, 32));
    infoButton->setText(tr("Information"));
    infoButton->setFixedWidth(90);
    infoButton->setFixedHeight(70);
    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 panels.
    //The updates page.
    mUpdatesPanel = new QScrollArea;
    mUpdatesWidget = new QWidget;
    QHBoxLayout *updatesHeaderLayout = new QHBoxLayout;
    QVBoxLayout *updatesHeaderTextLayout = new QVBoxLayout;
    QVBoxLayout *updatesHeaderSettLayout = new QVBoxLayout;

    mUpdatesHeader =
        new QLabel("<h2>" + tr("Updates (%1/%2)").arg(2).arg(4)+ "</h2>");
    mLastCertUpdate =
        new QLabel(tr("Last certificate update: %1").arg("today"));
    mLastSWupdate =
        new QLabel(tr("Last Software update: %1").arg("today"));
    updatesHeaderTextLayout->addWidget(mUpdatesHeader);
    updatesHeaderTextLayout->addWidget(mLastCertUpdate);
    updatesHeaderTextLayout->addWidget(mLastSWupdate);

    QPushButton *searchUpdates = new QPushButton(tr("Search for Updates"));
    connect(searchUpdates, SIGNAL(clicked()), this, SLOT(checkUpdates()));
    updatesHeaderSettLayout->insertStretch(0, 10);
    updatesHeaderSettLayout->addWidget(searchUpdates);
    updatesHeaderLayout->addLayout(updatesHeaderTextLayout);
    updatesHeaderLayout->insertStretch(1, 10);
    updatesHeaderLayout->addLayout(updatesHeaderSettLayout);

    QVBoxLayout *updatesCenterLayout = new QVBoxLayout;
    QHBoxLayout *updatesNewLayout = new QHBoxLayout;
    QHBoxLayout *updatesRemoveLayout = new QHBoxLayout;
    QHBoxLayout *updatesManualLayout = new QHBoxLayout;
    QLabel *updatesTip =
        new QLabel(tr("The following certificate changes are recommended."));
    mUpdatesNewCertificates =
        new QLabel("<h3>" +
            tr("Install new Certificates (%1/%2)").arg(2).arg(2) +
            "</h3>");
    QPushButton *updatesDetailsNew = new QPushButton(tr("Details"));
    connect(updatesDetailsNew,
        SIGNAL(clicked()),
        this,
        SLOT(toggleUpdatesNew()));
    updatesNewLayout->addWidget(mUpdatesNewCertificates);
    updatesNewLayout->addWidget(updatesDetailsNew);
    updatesNewLayout->insertStretch(2, 10);
    mUpdatesNew = new CertificateListWidget(this);
    connect(mUpdatesNew, SIGNAL(certListChanged(int)),
        this, SLOT(listChanged(int)));
    mUpdatesNew->hide();

    mUpdatesRemoveCertificates =
        new QLabel("<h3>" +
            tr("Remove insecure Certificates (%1/%2)").arg(2).arg(2) +
            "</h3>");
    QPushButton *updatesDetailsRemove = new QPushButton(tr("Details"));
    connect(updatesDetailsRemove,
        SIGNAL(clicked()),
        this,
        SLOT(toggleUpdatesRemove()));
    updatesRemoveLayout->addWidget(mUpdatesRemoveCertificates);
    updatesRemoveLayout->addWidget(updatesDetailsRemove);
    updatesRemoveLayout->insertStretch(2, 10);
    mUpdatesRemove = new CertificateListWidget(this);
    connect(mUpdatesRemove, SIGNAL(certListChanged(int)),
        this, SLOT(listChanged(int)));
    mUpdatesRemove->hide();

    mUpdatesManualCertificates =
        new QLabel("<h3>" +
            tr("Manualy changed Certificates (%1)").arg(2) +
            "</h3>");
    QPushButton *updatesDetailsManual = new QPushButton(tr("Details"));
    connect(updatesDetailsManual,
        SIGNAL(clicked()),
        this,
        SLOT(toggleUpdatesManual()));
    updatesManualLayout->addWidget(mUpdatesManualCertificates);
    updatesManualLayout->addWidget(updatesDetailsManual);
    updatesManualLayout->insertStretch(2, 10);
    mUpdatesManual = new CertificateListWidget(this);
    mUpdatesManual->hide();
    connect(mUpdatesManual, SIGNAL(certChanged(bool, const Certificate&)),
        this, SLOT(removeFromManual(bool, const Certificate&)));

    QHBoxLayout *updatesBottomLayout = new QHBoxLayout;
    QPushButton *quitButton = new QPushButton(tr("Quit without saving"));
    QPushButton *installButton = new QPushButton(tr("Update"));
#ifdef Q_OS_WIN
    if (is_admin()) {
        QIcon uacShield = QApplication::style()->standardIcon(QStyle::SP_VistaShield);
        installButton->setIcon(uacShield);
    }
#endif
    connect(quitButton, SIGNAL(clicked()), this, SLOT(closeApp()));
    connect(installButton, SIGNAL(clicked()), this, SLOT(installCerts()));
    updatesBottomLayout->insertStretch(0, 10);
    updatesBottomLayout->addWidget(installButton);
    updatesBottomLayout->addWidget(quitButton);
    updatesBottomLayout->setAlignment(Qt::AlignBottom);

    updatesNewLayout->setAlignment(Qt::AlignTop);
    updatesRemoveLayout->setAlignment(Qt::AlignTop);
    updatesManualLayout->setAlignment(Qt::AlignTop);
    updatesCenterLayout->addWidget(updatesTip, 0, Qt::AlignTop);
    updatesCenterLayout->addLayout(updatesNewLayout);
//    updatesCenterLayout->setStretchFactor(updatesNewLayout, 100);
    updatesCenterLayout->addWidget(mUpdatesNew, 1, Qt::AlignTop);
    updatesCenterLayout->addLayout(updatesRemoveLayout);
//    updatesCenterLayout->setStretchFactor(updatesRemoveLayout, 100);
    updatesCenterLayout->addWidget(mUpdatesRemove, 1, Qt::AlignTop);
    updatesCenterLayout->addLayout(updatesManualLayout);
//    updatesCenterLayout->setStretchFactor(updatesManualLayout, 100);
    updatesCenterLayout->addWidget(mUpdatesManual, 1, Qt::AlignTop);
    updatesCenterLayout->insertStretch(7, 0);
    updatesCenterLayout->addLayout(updatesBottomLayout);


    QFrame *updatesPanelSeparator = new QFrame();
    updatesPanelSeparator->setFrameShape(QFrame::HLine);
    updatesPanelSeparator->setFrameShadow(QFrame::Sunken);
    updatesPanelLayout->addLayout(updatesHeaderLayout);
    updatesPanelLayout->addWidget(updatesPanelSeparator);
    updatesPanelLayout->addLayout(updatesCenterLayout);
    updatesPanelLayout->setStretchFactor(updatesCenterLayout, 1);
    //updatesPanelLayout->insertStretch(3, 2);
    //updatesPanelLayout->addLayout(updatesBottomLayout);
    mUpdatesWidget->setLayout(updatesPanelLayout);
    mUpdatesWidget->setMinimumSize(QSize(820, 440));
    mUpdatesPanel->setWidget(mUpdatesWidget);

    // Panel for trusted certificates.
    mInstallPanel = 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 root"
        " certificates was created by the BSI. The BSI vaidates authenticity,"
        " security and actuality of these certificates"));
    installHeaderLayout->addWidget(installHeaderLabel);
    installHeaderLayout->addWidget(installHeaderText);

    QLabel *installCenterText = new QLabel(tr("Please choose the certificates"
        " you want to trust. TrustBridge will install these certificates for"
        " secure communication via email or the internet (e.g. IE, Firefox,"
        " Thunderbird)"));
    installCenterLayout->addWidget(installCenterText);
    mInstallList = new CertificateListWidget(this);
    connect(mInstallList, SIGNAL(certChanged(bool, const Certificate&)),
        this, SLOT(toggleInManual(bool, const Certificate&)));

    installPanelLayout->addLayout(installHeaderLayout);
    installPanelLayout->addLayout(installCenterLayout);
    installPanelLayout->addWidget(mInstallList);
    mInstallPanel->setLayout(installPanelLayout);

    // Panel for insecure certificates.
    mRemovePanel = new QScrollArea;
    QVBoxLayout *removePanelLayout = new QVBoxLayout;
    QVBoxLayout *removeHeaderLayout = new QVBoxLayout;
    QVBoxLayout *removeCenterLayout = new QVBoxLayout;

    QLabel *removeHeaderLabel =
        new QLabel("<h2>" + tr("Insecure Certificates") + "</h2>");
    QLabel *removeHeaderText = new QLabel(tr("The following list of root"
        " certificates was created by the BSI. The BSI validates authenticity,"
        " security and actuality of these certificates"));
    removeHeaderLayout->addWidget(removeHeaderLabel);
    removeHeaderLayout->addWidget(removeHeaderText);

    QLabel *removeCenterText = new QLabel(tr("Please choose the certificates"
        " you want to remove. TrustBridge will remove these certificates."));
    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);
    removePanelLayout->addWidget(mRemoveList);
    mRemovePanel->setLayout(removePanelLayout);

    // Info panel.
    mInfoPanel = new QScrollArea;

    QVBoxLayout *infoPanelLayout = new QVBoxLayout(this);
    QHBoxLayout *infoHeaderLayout = new QHBoxLayout;
    QVBoxLayout *infoHeaderTextLayout = new QVBoxLayout;
    QVBoxLayout *infoCenterLayout = new QVBoxLayout;

    QImage *infoLogoImage = new QImage(":/img/logo.png");
    QLabel *infoLogo = new QLabel;
    infoLogo->setBackgroundRole(QPalette::Base);
    infoLogo->setPixmap(QPixmap::fromImage(*infoLogoImage));
    QLabel *infoTitle = new QLabel("<h1>" + tr("TrustBridge") + "</h1>");
    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(infoTitle);
    infoHeaderTextLayout->addWidget(appVersion);
    infoHeaderLayout->addWidget(infoLogo);
    infoHeaderLayout->addLayout(infoHeaderTextLayout);
    infoHeaderLayout->insertStretch(2, 10);

    QLabel *textDesc = new QLabel(tr("TrustBridge is a root certificate"
        " installer for Windows and Linux."));
    textDesc->setTextInteractionFlags(
        Qt::TextSelectableByMouse |
        Qt::TextSelectableByKeyboard);
    QLabel *textManage = new QLabel(tr("The root certificate lists are managed"
        " by the German <a href=\"https://www.bsi.bund.de\">"
        "Federal Office for Information Security (BSI)</a>.\n\n"));
    textManage->setTextInteractionFlags(
        Qt::TextBrowserInteraction |
        Qt::TextSelectableByKeyboard);
    QLabel *textDevel = new QLabel(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).\n\n"));
    textDevel->setTextInteractionFlags(
        Qt::TextBrowserInteraction |
        Qt::TextSelectableByKeyboard);
    QLabel *textLicense = new QLabel(tr("TrustBridge is Free Software licensed"
        " under GNU GPL v2+.\n\nCopyright (C) 2014 by Bundesamt für Sicherheit"
        " in der Informationstechnik"));
    textLicense->setTextInteractionFlags(
        Qt::TextSelectableByMouse |
        Qt::TextSelectableByKeyboard);

    infoCenterLayout->addWidget(infoHeaderSeparator);
    infoCenterLayout->addWidget(textDesc);
    infoCenterLayout->addWidget(textManage);
    infoCenterLayout->addWidget(textDevel);
    infoCenterLayout->addWidget(textLicense);
    infoCenterLayout->insertSpacing(2, 10);
    infoCenterLayout->insertSpacing(4, 10);
    infoCenterLayout->insertSpacing(6, 10);
    infoCenterLayout->insertStretch(8, 10);

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

    // The main layout for pages.
    mInstallPanel->hide();
    mRemovePanel->hide();
    mInfoPanel->hide();
    containerLayout->addWidget(mUpdatesPanel);
    containerLayout->addWidget(mInstallPanel);
    containerLayout->addWidget(mRemovePanel);
    containerLayout->addWidget(mInfoPanel);

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

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

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

void MainWindow::listChanged(int selected)
{
    mUpdatesHeader->setText("<h2>" + tr("Updates (%1/%2)")
            .arg(mUpdatesRemove->selectedCertCount() + mUpdatesNew->selectedCertCount())
            .arg(mUpdatesRemove->certificates().size() + mUpdatesNew->certificates().size()) +
            "</h2>");
    mUpdatesNewCertificates->setText("<h3>" +
            tr("Install new Certificates (%1/%2)")
            .arg(mUpdatesNew->selectedCertCount())
            .arg(mUpdatesNew->certificates().size()) +
            "</h3>");
    mUpdatesRemoveCertificates->setText("<h3>" +
            tr("Remove insecure Certificates (%1/%2)")
            .arg(mUpdatesRemove->selectedCertCount())
            .arg(mUpdatesRemove->certificates().size()) +
            "</h3>");
}

void MainWindow::loadCertificateList()
{
    /* TODO: if nothing is available (neither old nor new) add some progress
     * indication */
    mInstallList->clear();
    mUpdatesNew->clear();
    mRemoveList->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);
            }
        }
        // Set the date of the old list.
//        mCurrentListDate->setText(tr("Current List Date: %1")
//            .arg(mInstalledList.date().toString()));
    }
    else {
        // Sort and filter both lists.
        foreach (const Certificate &cert, mListToInstall.getCertificates()) {
            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);
                }
            }
            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);
                }
            }
        }
        mUpdatesHeader->setText("<h2>" +
            tr("Updates (%1/%2)")
                .arg(mUpdatesNew->selectedCertCount() + mUpdatesRemove->selectedCertCount())
                .arg(newRemoveCerts.size() + newInstallCerts.size()) + "</h2>");
        mLastCertUpdate->setText(tr("Last certificate update: %1")
            .arg(mInstalledList.date().toString()));
/*        mCurrentListDate->setText(tr("Current List Date: %1")
            .arg(mInstalledList.date().toString()));
        mNewListDate->setText(tr("New List Date: %1").arg(mListToInstall.date().toString()));*/
    }
    mUpdatesNewCertificates->setText("<h3>" +
            tr("Install new Certificates (%1/%2)")
            .arg(mUpdatesNew->selectedCertCount())
            .arg(mUpdatesNew->certificates().size()) +
            "</h3>");

    mUpdatesRemoveCertificates->setText("<h3>" +
            tr("Remove insecure Certificates (%1/%2)")
            .arg(mUpdatesRemove->selectedCertCount())
            .arg(mUpdatesRemove->certificates().size()) +
            "</h3>");
    mUpdatesManualCertificates->setText("<h3>" +
            tr("Manualy changed Certificates (%1)").arg(0) +
            "</h3>");
}

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

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

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

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

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

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

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

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

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

    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("Manualy changed Certificates (%1)")
                .arg(mUpdatesManual->certificates().size()) +
            "</h3>");
}

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

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

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

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

void MainWindow::togglePages(int button)
{
    mUpdatesPanel->hide();
    mInstallPanel->hide();
    mRemovePanel->hide();
    mInfoPanel->hide();
    switch(button) {
    case 0: mUpdatesPanel->show(); break;
    case 1: mInstallPanel->show(); break;
    case 2: mRemovePanel->show(); break;
    case 3: mInfoPanel->show(); break;
    default: mUpdatesPanel->show(); break;
    }
    return;
}

void MainWindow::toggleUpdatesNew() {
    QSize old = mUpdatesWidget->size();
    if (!mUpdatesNew->isVisible()) {
        mUpdatesNew->show();
        if (mUpdatesNewSize == 0) {
            mUpdatesNewSize = mUpdatesNew->height();
        }
        mUpdatesWidget->resize(old.width(), old.height() + mUpdatesNewSize);
    }
    else {
        mUpdatesWidget->resize(old.width(), old.height() - mUpdatesNew->height());
        mUpdatesNew->hide();
    }
}

void MainWindow::toggleUpdatesRemove() {
    QSize old = mUpdatesWidget->size();
    if (!mUpdatesRemove->isVisible()) {
        mUpdatesRemove->show();
        if (mUpdatesRemoveSize == 0) {
            mUpdatesRemoveSize = mUpdatesRemove->height();
        }
        mUpdatesWidget->resize(old.width(), old.height() + mUpdatesRemoveSize);
    }
    else {
        mUpdatesWidget->resize(old.width(), old.height() - mUpdatesRemove->height());
        mUpdatesRemove->hide();
    }
}

void MainWindow::toggleUpdatesManual() {
    QSize old = mUpdatesWidget->size();
    if (!mUpdatesManual->isVisible()) {
        mUpdatesManual->show();
        if (mUpdatesManualSize == 0) {
            mUpdatesManualSize = mUpdatesManual->height();
        }
        mUpdatesWidget->resize(old.width(), old.height() + mUpdatesManualSize);
    }
    else {
        mUpdatesWidget->resize(old.width(), old.height() - mUpdatesManual->height());
        mUpdatesManual->hide();
    }
}

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