view ui/mainwindow.cpp @ 1371:23df332b2a4c

(issue179) Read install signature timestamp from config This also changes the way the sigDt is propgated to the MainWindow. It no longer uses the settings but hands it over as a parameter directly.
author Andre Heinecke <andre.heinecke@intevation.de>
date Mon, 24 Nov 2014 15:48:49 +0100
parents 3d7ddf698480
children 00fcb9c4d16b
line wrap: on
line source
/* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU GPL (v>=2)
 * and comes with ABSOLUTELY NO WARRANTY!
 * See LICENSE.txt for details.
 */
#include "mainwindow.h"

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

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

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

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

#ifndef APPNAME
#define APPNAME "TrustBridge"
#endif

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

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

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

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

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

/** @brief get the interval in milliseconds until the next update try.
 *
 * @param [in] tries the number of unsuccessful tries already done.
 *
 * @returns a positive value for the time in ms that should be waited.
 * -1 in case the software should abort. */
static int getNextUpdateInterval(int tries)
{
    if (tries < 2) {
        // 5-10 minutes for the first two checks
        return ((5 + (qrand() % 5)) * 60 * 1000);
    }
    if (tries < 4) {
        // 15 minutes
        return 15 * 60 * 1000;
    }
    if (tries < 6) {
        // 30 minutes
        return 30 * 60 * 1000;
    }
    if (tries < 7) {
        return 60;
    }
    return -1;
}

MainWindow::MainWindow(bool trayMode, const QDateTime& sigDt):
    mTrayMode(trayMode),
    mManualDetailsShown(false),
    mFailedConnections(0),
    mSigDt(sigDt)
{
    createActions();
    createTrayIcon();
    setupGUI();
    resize(900, 600);
    setMinimumWidth(760);
    qRegisterMetaType<SSLConnection::ErrorCode>("SSLConnection::ErrorCode");
    qRegisterMetaType<Certificate::Status>("Certificate::Status");

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

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

    if (mSettings.value("ShowOnNextStart").toBool()) {
        mSettings.remove("ShowOnNextStart");
        show();
    } else if (!trayMode) {
        show();
    }
}

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

void MainWindow::messageClicked()
{
    if (mCurState == NewSoftwareAvailable) {
        verifySWData();
        QString swFileName = mSettings.value("Software/available").toString();
        if (swFileName.isEmpty()) {
            checkUpdates(true);
            mCurState = DownloadingSW;
            return;
        }
        installNewSW(swFileName,
                mSettings.value("Software/availableDate").toDateTime());
    } else {
        show();
    }
}

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

    if (!mTrayIcon->isVisible() && !mTrayIcon->isAlternative()) {
        mTrayIcon->show();
        /* When the message is shown before the tray icon is fully visble
         * and becoming visible may be delayed on some desktop environments
         * the message will pop up somehere on the screen and not over
         * the trayicon. So we delay here.*/
        QTimer::singleShot(2000, this, SLOT(showMessage()));
    } else if (mCurState == NewSoftwareAvailable || !isVisible()) {
        if (mCurState == NewListAvailable) {
            mTrayIcon->showMessage(QApplication::applicationName(), mCurMessage,
                                   QSystemTrayIcon::Information, 10000,
                                   tr("Show recommendations"));
        } else if (mCurState == NewSoftwareAvailable || !mTrayIcon->isAlternative()) {
            /* Only show new list or new software in alternative as
             * the current tray icon alternative is too invasive for pure
             * informational messages. */
            mTrayIcon->showMessage(QApplication::applicationName(), mCurMessage,
                                   QSystemTrayIcon::Information, 10000);
        }
        mMessageTimer->start(); // Restart the timer so that we don't spam
    }
}

void MainWindow::verifyListData()
{
    QString availableFileName = mSettings.value("List/available").toString();
    QString installedFileName = mSettings.value("List/installed").toString();
    if (!availableFileName.isEmpty()) {
        mListToInstall.readList(availableFileName.toUtf8().constData());
        if (!mListToInstall.isValid()) {
            handleLTE(lteInvalidList);
            mCurState = TransferError;
            QFile::remove(availableFileName);
            mSettings.remove("List/available");
            mSettings.remove("List/availableDate");
        } else {
            handleLTE(lteInvalidList, true);
        }
    } else {
        // Make sure the available notation is also removed
        mSettings.remove("List/available");
        mSettings.remove("List/availableDate");
    }

    if (!installedFileName.isEmpty()) {
        mInstalledList.readList(installedFileName.toUtf8().constData());
        if (!mInstalledList.isValid()) {
            // Probably a bug when Qt fileName is encoded and cFileName
            // fails because of this. This needs a unit test!
            // Maybe check that the file is in our data directory
            QFile::remove(installedFileName);
            mSettings.remove("List/installed");
            mSettings.remove("List/installedDate");
        }
    } else {
        mSettings.remove("List/installed");
        mSettings.remove("List/installedDate");
    }
}

void MainWindow::verifySWData()
{
    QString swFileName = mSettings.value("Software/available").toString();

    if (swFileName.isEmpty()) {
        qDebug() << "Date set but no fileName";
        mSettings.remove("Software/availableDate");
        return;
    }

    QFileInfo fi(swFileName);
    if (!fi.exists()) {
        mSettings.remove("Software/available");
        mSettings.remove("Software/availableDate");
        qDebug() << "Software does not yet exist.";
        return;
    }
    if (!fi.isExecutable()) {
        qWarning() << "Downloaded file: " << swFileName << " is not executable.";
        QFile::remove(swFileName);
        mSettings.remove("Software/available");
        mSettings.remove("Software/availableDate");
        return;
    }
    bin_verify_result verifyResult = verify_binary(swFileName.toUtf8().constData(),
            swFileName.toUtf8().size());
    QDateTime sigDt = QDateTime::fromTime_t (verifyResult.sig_time);

    if (verifyResult.result != VerifyValid || (mSigDt.isValid() && sigDt <= mSigDt)) {
        handleLTE(lteInvalidSoftware);
        if (verifyResult.result != VerifyValid) {
            qDebug() << "Failed to verify downloaded data.";
        } else {
            qDebug() << "Software update was signed at: " << sigDt;
            qDebug() << "But the installed software was signed on: " << mSigDt;
            if (verifyResult.fptr) {
                fclose(verifyResult.fptr);
            }
        }
        QFile::remove(swFileName);
        mSettings.remove("Software/available");
        mSettings.remove("Software/availableDate");
        return;
    }
    handleLTE(lteInvalidSoftware, true); /* Reset error state */
    fclose(verifyResult.fptr);
}

void MainWindow::handleNewList(const QString& fileName, const QDateTime& modDate) {

    if (mSettings.value("List/available").toString() == fileName &&
        getState() == NewListAvailable) {
        qDebug() << "List already handled";
        return;
    } else {
        qDebug() << "Handling list";
    }
    mSettings.setValue("List/available", fileName);
    mSettings.setValue("List/availableDate", modDate);

    verifyListData();
    if (!mListToInstall.isValid() || mListToInstall.date() <= mInstalledList.date()) {
        if ( mListToInstall.date() <= mInstalledList.date()) {
            qDebug() << "Newest list on the server is older then the installed list. ";
            qDebug() << "Installed: " << mInstalledList.date();
            qDebug() << "Available: " << mListToInstall.date();
        } else {
            qDebug() << "Failed to verify list.";
        }
        handleLTE(lteInvalidList);
        /* Downloader provided invalid files */

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

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

    mSettings.sync();
    showMessage();
}

QString MainWindow::getPrettyInstallerName(QString realFileName) {
    QTemporaryDir tDir;
    if (!tDir.isValid()) {
        qDebug () << "Failed to create temporary directory.";
        showErrorMessage (tr("Failed to create temporary directory.") + "\n" +
                tr("Please ensure that you have the access rights to write in "
                   "the temporary directory and that there is at least 20MB free "
                   "disk space available."));
        return QString();
    }
    QString targetPath = tDir.path() + "/" + QObject::tr("TrustBridge-Updater",
            "Used as filename for the updater. Only use ASCII please.");

    tDir.setAutoRemove(false);
#ifdef WIN32
    targetPath += ".exe";
#endif
    if (!QFile::copy(realFileName, targetPath)) {
        showErrorMessage (tr("Failed to create a temporary copy of the installer.") + "\n" +
                tr("Please ensure that you have the access rights to write in "
                   "the temporary directory and that there is at least 20MB free "
                   "disk space available."));
        qDebug() << "Failed to create temporary copy of installer.";
    }
    return targetPath;
}

void MainWindow::updaterFinished(int exitCode, QProcess::ExitStatus status)
{
    if (status != QProcess::NormalExit) {
        syslog_error_printf("Update failed.\n");
        qDebug() << "Failed to install update.";
        return;
    }
    if (exitCode != 0) {
        qDebug() << "Update not installed with error: " << exitCode;
        return;
    }
    qDebug() << "Restarting";
    ProcessHelp::cleanUp();
    QProcess::startDetached(qApp->applicationFilePath());
    qApp->quit();
}

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

    /* Reset available information. */
    mSettings.setValue("Software/available", fileName);
    mSettings.setValue("Software/availableDate", modDate);

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

    if (filePath.isEmpty()) {
        qDebug() << "Failed to copy updater to temporary location.";
        return;
    }
    mSettings.setValue("Software/Updater", filePath); /* So it can be deleted
                                                         on next start */
    mSettings.sync();

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

    QDateTime sigDt = QDateTime::fromTime_t (vres.sig_time);

    if (vres.result != VerifyValid || (mSigDt.isValid() && sigDt <= mSigDt)) {
        handleLTE(lteInvalidSoftware);
        if (vres.result != VerifyValid) {
            qDebug() << "Failed to verify installer.";
        } else {
            qDebug() << "Software update was signed at: " << sigDt;
            qDebug() << "But the installed software was signed on: " << mSigDt;
            if (vres.fptr) {
                fclose(vres.fptr);
            }
        }
        QFile::remove(filePath);
        mSettings.remove("Software/available");
        mSettings.remove("Software/availableDate");
        return;
    }
    handleLTE(lteInvalidSoftware, true);
    QFileInfo fi(QCoreApplication::applicationFilePath());
    QDir installDir = fi.absoluteDir();

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

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

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

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

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

    qDebug() << "Starting process: " << filePath
             << " with arguments: " << parameters;

    if (!ShellExecuteExW(&shExecInfo)) {
        /* Execution failed, maybe the user aborted the UAC check? */
        char* errmsg = getLastErrorMsg();
        QString qerrmsg = QString::fromUtf8(errmsg);
        free(errmsg);
        qDebug() << "Failed to start process: " << qerrmsg;
        setState(NewSoftwareAvailable);
        fclose(vres.fptr);
        return;
    }
#else /* WIN32 */
    /* On linux installDir is <prefix>/bin */
    QStringList parameters;
    installDir.cdUp();
    parameters << "--prefix" << installDir.path();
    parameters << "--update";
    bool sudo_started = false;
    bool use_sudo = is_admin() && is_system_install();

    QProcess *updaterProcess = new QProcess();

    if (use_sudo) {
        QStringList sudoPrograms;
        sudoPrograms << "pkexec" << "kdesudo" << "sudo";
        QStringList sudoParams;
        sudoParams << filePath;
        sudoParams << parameters;
        updaterProcess->setArguments(sudoParams);

#if 0
        updaterProcess->setStandardErrorFile("/tmp/tb-inst-err.log");
        updaterProcess->setStandardOutputFile("/tmp/tb-inst-out.log");
#endif

        foreach (const QString &sProg, sudoPrograms) {
            qDebug() << "Starting process " << sProg <<" params: " << sudoParams;
            updaterProcess->setProgram(sProg);
            updaterProcess->start();
            if (!updaterProcess->waitForStarted() ||
                updaterProcess->state() == QProcess::NotRunning) {
                continue;
            } else {
                sudo_started = true;
                connect(updaterProcess, SIGNAL(finished(int, QProcess::ExitStatus)),
                        this, SLOT(updaterFinished(int, QProcess::ExitStatus)));
                break;
            }
        }
    }
    if (!sudo_started) {
        qDebug() << "Starting process " << filePath <<" params: " << parameters;
        updaterProcess->setArguments(parameters);
        updaterProcess->setProgram(filePath);
        updaterProcess->start();

        if (!updaterProcess->waitForStarted() ||
            updaterProcess->state() == QProcess::NotRunning) {
            qDebug() << "Failed to start process.";
        }
        connect(updaterProcess, SIGNAL(finished(int, QProcess::ExitStatus)),
                this, SLOT(updaterFinished(int, QProcess::ExitStatus)));
        return;
    }

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

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

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

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

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

    if (!mSettings.contains("Software/installedDate") ||
          mSettings.value("Software/installedVersion").toString() != QApplication::applicationVersion()) {
        /* This should only happen on initial startup and after an update has
         * been installed */
        getLastModForCurrentVersion();
        return;
    }
    QDateTime listInstalledLastMod = mSettings.value("List/installedDate").toDateTime();
    QDateTime swInstalledLastMod = mSettings.value("Software/installedDate").toDateTime();
    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 {
        setState(DownloadingSW);
        connect(downloader, SIGNAL(newSoftwareAvailable(const QString&, const QDateTime&)),
                this, SLOT(installNewSW(const QString&, const QDateTime&)));
    }

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

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

void MainWindow::getLastModForCurrentVersion()
{
    QString softwareVersion = QString::fromLatin1(SW_RESOURCE_VERSION).arg(
        QApplication::applicationVersion());
    qDebug() << "Getting last modified date for: " << softwareVersion;
    QString listResource = QString::fromLatin1(LIST_RESOURCE);
    Downloader* downloader = new Downloader(this,
                                            QString::fromLatin1(SERVER_URL),
                                            QByteArray(),
                                            QDateTime::currentDateTime(),
                                            QDateTime::currentDateTime(),
                                            softwareVersion,
                                            listResource,
                                            false);
    connect(downloader, SIGNAL(finished()), downloader, SLOT(deleteLater()));
    connect(downloader, SIGNAL(error(const QString &, SSLConnection::ErrorCode)),
            this, SLOT(downloaderError(const QString &, SSLConnection::ErrorCode)));
    connect(downloader, SIGNAL(lastModifiedDate(const QDateTime&)),
        this, SLOT(setLastModifiedSWDate(const QDateTime&)));
    connect(this, SIGNAL(destroyed(QObject*)), downloader, SLOT(quit()));
    downloader->start();
}

void MainWindow::setLastModifiedSWDate(const QDateTime &date)
{
    QDateTime swAvailableLastMod = mSettings.value("Software/availableDate").toDateTime();

    if (swAvailableLastMod.isValid() && date.isValid()) {
        if (date >= swAvailableLastMod) {
            qDebug() << "Installed an update: " << date <<
                " available was " << swAvailableLastMod;
            syslog_info_printf ("Software has been updated to version: %s\n",
                QApplication::applicationVersion().toUtf8().constData());
            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");
        }
    }

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

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

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

void MainWindow::createTrayIcon()
{
    QIcon trayImg(":/img/tray_22.png");
    trayImg.addFile(":/img/tray_48.png", QSize(48,48));

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

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

    mTrayIcon->setIcon(trayImg);

    if (mTrayIcon->isAlternative()) {
        /* On unity (the alternative notification usage)
         * we want to use the logo as window icon.*/
        setWindowIcon(QIcon(":/img/logo.png"));
    } else {
        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 BSI.<br/><br/>") +
    tr("TrustBridge is Free Software licensed"
        " under GNU GPL v>=3.<br/>Copyright (C) 2014 by Bundesamt für Sicherheit"
        " in der Informationstechnik<br/><br/>") +
    tr("TrustBridge uses several Free Software components with different licenses:") +
	"<ul><li>TrustBridge source code (GPL v>=2)" +
	"<li>Qt (LGPL v==2.1)" +
	"<li>PolarSSL (GPL v>=2)" +
	"<li>Oxygen-Icons (LGPL v==3)" +
	"<li>Mozilla NSS (Mozilla Public License v2)" +
	"<li>libcurl (The curl license)</ul>" +
    tr("You will find the legally binding details in the 'licenses' directory "
       "where TrustBridge is installed<br/>"
       "or in the corresponding revision of the "
       "<a href=\"https://wald.intevation.org/hg/trustbridge/file/tip/licenses\">TrustBridge code repository</a>."));
    textDesc->setTextFormat(Qt::RichText);
    textDesc->setTextInteractionFlags(
        Qt::TextSelectableByMouse |
        Qt::TextSelectableByKeyboard |
        Qt::LinksAccessibleByMouse);
    textDesc->setOpenExternalLinks(true);

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

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

    infoCenterLayout->insertStretch(8, 10);

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

    return theWidget;
}

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

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

    QGridLayout *detailsLayout = new QGridLayout;

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

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

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

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

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

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

    updatesMainLayout->addLayout(updatesHeaderLayout);

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

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

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

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

    updatesCenterLayout->addLayout(detailsLayout);

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

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

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

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

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

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

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


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

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

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

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

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

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

    installPanelLayout->addWidget(scrollArea);

    theWidget->setLayout(installPanelLayout);

    return theWidget;
}

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

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

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

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

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

    return theWidget;
}

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

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

    // The header (icon, about text)
    QImage *logoImage = new QImage(":/img/logo.png");
    QLabel *logo = new QLabel;
    logo->setBackgroundRole(QPalette::Base);
    logo->setPixmap(QPixmap::fromImage(*logoImage));
    QLabel *title = new QLabel("<h1>" + QString::fromLatin1(APPNAME) + "</h1>");
    QLabel *subTitle = new QLabel(tr("Trust in your digital communication"));
    QLabel *swVersion = new QLabel(QString::fromLatin1("<i>") +
            tr("Version") + " " + QApplication::applicationVersion() +
            QString::fromLatin1(" </i>"));

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

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

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

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

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

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

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

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

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

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

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

    /* The main pages.*/

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

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

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

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

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

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

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

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

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

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

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

    if (mUpdatesNew->certificates().size()) {
        mUpdatesNewCertificates->setText("<h3>" +
                tr("Install new trusted certificates (%1/%2)")
                .arg(mUpdatesNew->selectedCertCount())
                .arg(mUpdatesNew->certificates().size()) +
                "</h3>");
        if (!mUpdatesNew->isVisible()) {
            activateDetailsButton(mUpdatesDetailsNew);
        }
        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();
        if (!mUpdatesRemove->isVisible()) {
            activateDetailsButton(mUpdatesDetailsRemove);
        }
    } else {
        mUpdatesRemoveCertificates->hide();
        mUpdatesDetailsRemove->hide();
        mUpdatesRemove->hide();
    }

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

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

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

    listChanged(0);
    setUpdatesEnabled(true);
}

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

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

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

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

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

    QProgressDialog *progress = new QProgressDialog(this);
    progress->setWindowModality(Qt::WindowModal);
    progress->setLabelText(tr("Installing certificates..."));
    progress->setCancelButton(0);
    progress->setRange(0,0);
    progress->setMinimumDuration(0);
    progress->show();

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

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

    syslog_info_printf ("Installing certificate list: '%s' Version '%s'\n",
            instList->fileName().toUtf8().constData(),
            instList->date().toString().toUtf8().constData());
    /* Clean up object and progress dialog */
    connect(instWrap, SIGNAL(finished()), instWrap, SLOT(deleteLater()));
    connect(instWrap, SIGNAL(finished()), progress, SLOT(deleteLater()));
    connect(instWrap, SIGNAL(finished()), progress, SLOT(cancel()));
    connect(instWrap, SIGNAL(installationSuccessful()),
            this, SLOT(installerSuccess()));
    connect(instWrap, SIGNAL(error(const QString &)),
            this, SLOT(installerError(const QString &)));
    instWrap->start();

    if (!saveUnselectedCertificates(unselected)) {
        qWarning() << "Failed to save previosly unselected certificates.";
    }

}

void MainWindow::loadUnselectedCertificates()
{
    mPreviouslyUnselected.clear();
    mSettings.beginGroup("unselected");
    QStringList keys = mSettings.allKeys();
    foreach (const QString &key, keys) {
        mPreviouslyUnselected << mSettings.value(key, QString()).toString();
    }
    mSettings.endGroup();
}

bool MainWindow::saveUnselectedCertificates(QStringList unselected)
{
    mPreviouslyUnselected.clear();
    mSettings.beginGroup("unselected");
    mSettings.remove(""); /* Clears old choices */
    for (int i = 0; i < unselected.size(); i++) {
        QString key = QString::fromLatin1("cert%1").arg(i);
        QString value = unselected.at(i);
        mSettings.setValue(key, value);
        mPreviouslyUnselected << value;
    }
    mSettings.endGroup();
    mSettings.sync();
    return mSettings.status() == QSettings::NoError;
}

void MainWindow::toggleInManual(bool state, const Certificate &cert)
{
    if (!mUpdatesManual->contains(cert)) {
        QToolButton* actionBtn = new QToolButton();
        QIcon btnIcon;
        if (mRemoveList->contains(cert)) {
            btnIcon.addFile(":/img/cert-to-be-removed-good-48.png", QSize(48, 48), QIcon::Normal, QIcon::On);
            btnIcon.addFile(":/img/cert-is-installed-bad-48.png", QSize(48, 48), QIcon::Normal, QIcon::Off);
            actionBtn->setProperty("ToolTip_On", tr("Certificate will be removed."));
            /* Off should never be possible here
             * As the manual change of removed certificates is disabled */
            actionBtn->setProperty("ToolTip_Off", tr("Certificate will not be removed."));
        } else {
            btnIcon.addFile(":/img/cert-to-be-installed-good-48.png", QSize(48, 48), QIcon::Normal, QIcon::On);
            btnIcon.addFile(":/img/cert-to-be-removed-bad-48.png", QSize(48, 48), QIcon::Normal, QIcon::Off);
            actionBtn->setProperty("ToolTip_On", tr("Certificate will be installed."));
            actionBtn->setProperty("ToolTip_Off", tr("Certificate will be removed."));
        }
        actionBtn->setIcon(btnIcon);
        mUpdatesManual->addCertificate(cert, state, actionBtn);
    }
    else {
        mUpdatesManual->removeCertificate(cert);
    }
}

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

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

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

void MainWindow::checkAndInstallCerts()
{
#ifdef WIN32
    /* 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;
#else
    /* On GNU Linux finding all firefox / thunderbird instances which
     * could be found on the filesystem is more difficult. So
     * for now we just show a generic warning dialog. */
    QMessageBox* warnMessage = new QMessageBox (QMessageBox::Information,
            tr("Firefox and Thunderbird certificate installation."),
            tr("Please close all running Firefox and Thunderbird instances "
                "before continuing with the installation."), QMessageBox::Ok | QMessageBox::No,
            this);
    warnMessage->button(QMessageBox::Ok)->setText(tr("Continue"));
    warnMessage->setDefaultButton(QMessageBox::Ok);
    warnMessage->button(QMessageBox::No)->setText(tr("Cancel"));
    warnMessage->setWindowIcon(windowIcon());
    warnMessage->setIconPixmap(QIcon(":/img/dialog-warning.png").pixmap(QSize(48,48)));

    connect(warnMessage->button(QMessageBox::Ok), SIGNAL(clicked()), this, SLOT(installCerts()));
    warnMessage->exec();
    return;
#endif
}

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

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

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

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

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

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

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

void MainWindow::updateCheckSuccess()
{
    if (getState() != TransferError) {
        const QDateTime now = QDateTime::currentDateTime();
        mSettings.setValue("lastUpdateCheck", now);
        mLastUpdateCheckContents->setText(QLocale::system().toString(now, DATETIME_FORMAT));
        mLastUpdateCheckContents->show();
        mLastUpdateCheck->show();
        syslog_info_printf(tr("Sucessfully checked for updates.").toUtf8().constData());
        handleLTE(lteNoConnection, true); /* Reset error state */
        handleLTE(lteInvalidCertificate, true);
        mFailedConnections = 0;
    } else if (!isVisible()) {
        int waitInterval = getNextUpdateInterval(mFailedConnections++);
        if (waitInterval < 0) {
            qDebug() << "Shutting down after " << mFailedConnections <<
                " tries to get an update connection.";
            closeApp();
            return;
        }
        qDebug() << "Waiting for " << waitInterval / 1000 << " seconds until the next try.";
        QTimer::singleShot(waitInterval, this, SLOT(checkUpdates()));
        return;
    }
    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);
}

void MainWindow::handleLTE(LongTimeErrors lte, bool reset)
{
    QString settingPrefix;
 //   qDebug() << "Handle LTE for " << lte << " Reset? : " << reset;
    switch (lte) {
        case lteInvalidSoftware:
            settingPrefix = "LTE/invalidSW";
            break;
        case lteInvalidList:
            settingPrefix = "LTE/invalidList";
            break;
        case lteInvalidCertificate:
            settingPrefix = "LTE/invalidCertificate";
            break;
        case lteNoConnection:
            settingPrefix = "LTE/noConnection";
            break;
        default:
            qDebug() << "Unhandled error. " << lte;
    }

    if (reset) {
        /* delete all values and be done */
        mSettings.remove(settingPrefix + "_lastSaved");
        mSettings.remove(settingPrefix + "_count");
        mSettings.remove(settingPrefix + "_lastShown");
        return;
    }

    QDateTime lastSaved = mSettings.value(settingPrefix + "_lastSaved").toDateTime();
    bool cnt_valid;
    int cnt = mSettings.value(settingPrefix + "_count").toInt(&cnt_valid);
    if (!cnt_valid) {
        cnt = 0;
    }

    if (!lastSaved.isValid() || lastSaved.daysTo(QDateTime::currentDateTime()) >= 1) {
        /* The error count is increased at most once a day */
        mSettings.setValue(settingPrefix + "_lastSaved", QDateTime::currentDateTime());
        mSettings.setValue(settingPrefix + "_count", ++cnt);
    }


    if (cnt < 7) {
        /* We are done */
        return;
    }
    /* A week has passed. Start showing the error. */
    QDateTime lastShown = mSettings.value(settingPrefix + "_lastShown").toDateTime();
    if (lastShown.isValid() && lastShown.daysTo(QDateTime::currentDateTime()) < 1) {
        /* Only show the error message once a day */
        return;
    }

    mSettings.setValue(settingPrefix + "_lastShown", QDateTime::currentDateTime());

    switch (lte) {
        case lteInvalidSoftware:
            showErrorMessage(tr("The integrity check for the available software update has "
                        "failed repeatedly.") + "\n" +
                        tr("Please contact your Support or the publisher of the Software."));
            break;
        case lteInvalidList:
            showErrorMessage(tr("The integrity check of the available certificates has "
                        "failed repeatedly.") + "\n" +
                        tr("Please contact your Support or the publisher of the Software."));
            break;
        case lteInvalidCertificate:
            showErrorMessage(tr("The authentication of the update server has "
                        "failed repeatedly.") + "\n" +
                        tr("Please contact your Support or the publisher of the Software."));
            break;
        case lteNoConnection:
            bool useProxy = mSettings.value("UseProxy", false).toBool();
            if (useProxy) {
                showErrorMessage(tr("The connection to the update server has "
                            "failed repeatedly.") + "\n" +
                            tr("Please check that the Proxy Server \"%1\" is available.").arg(
                                mSettings.value("ProxyURL").toString()));
            } else {
                showErrorMessage(tr("The connection to the update server has "
                            "failed repeatedly.") + "\n" +
                            tr("Please check your internet connection."));
            }
            break;
    }
}

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