view ui/mainwindow.cpp @ 754:27043d74dc90

(Issue25) Align header contents in their own column. We now also stretch column 3 so that the contents are aligned with the descriptive labels without a space in between. Sadly this causes the quit button to be resized to it's minimum instead of sharing the space with the installation button as the installation button is so large that it squeezes the push button.
author Andre Heinecke <andre.heinecke@intevation.de>
date Mon, 07 Jul 2014 12:38:33 +0200
parents fa0c99056208
children 60d3f59f0803 20ca94680003
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 <QGridLayout>
#include <QGroupBox>
#include <QSplitter>
#include <QLabel>
#include <QImage>
#include <QCheckBox>
#include <QButtonGroup>
#include <QToolButton>

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

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

#define DATETIME_FORMAT "dddd, d.MMMM yyyy HH:mm:ss"

#ifndef APPNAME
#define APPNAME "TrustBridge"
#endif

#define SERVER_URL "https://files.intevation.de:443"

#ifdef RELEASE_BUILD
# define LIST_RESOURCE "/users/aheinecke/zertifikatsliste.txt"
# ifdef Q_OS_WIN
#  define SW_RESOURCE_VERSION "/users/aheinecke/TrustBridge-%1.exe"
#  define SW_RESOURCE "/users/aheinecke/TrustBridge.exe"
# else
#  define SW_RESOURCE_VERSION "/users/aheinecke/TrustBridge-%1.sh"
#  define SW_RESOURCE "/users/aheinecke/TrustBridge.sh"
# endif
#else // RELEASE_BUILD
# define LIST_RESOURCE "/users/aheinecke/development/zertifikatsliste.txt"
# ifdef Q_OS_WIN
#  define SW_RESOURCE_VERSION "/users/aheinecke/development/TrustBridge-development.exe"
#  define SW_RESOURCE "/users/aheinecke/development/TrustBridge.exe"
# else
#  define SW_RESOURCE_VERSION "/users/aheinecke/development/TrustBridge-development.sh"
#  define SW_RESOURCE "/users/aheinecke/development/TrustBridge.sh"
# endif
#endif

MainWindow::MainWindow(bool trayMode):
    mTrayMode(trayMode)
{
    createActions();
    createTrayIcon();
    setupGUI();
    resize(1065, 600);
    setMinimumWidth(760);
    qRegisterMetaType<SSLConnection::ErrorCode>("SSLConnection::ErrorCode");
    qRegisterMetaType<Certificate::Status>("Certificate::Status");

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

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

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

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

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

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

void MainWindow::verifyListData()
{
    QString availableFileName = mSettings.value("List/available").toString();
    QString installedFileName = mSettings.value("List/installed").toString();
    if (!availableFileName.isEmpty()) {
        mListToInstall.readList(availableFileName.toLocal8Bit().constData());
        if (!mListToInstall.isValid()) {
            mCurState = TransferError;
            // Probably a bug when Qt fileName is encoded and cFileName
            // fails because of this. This needs a unit test!
            // Maybe check that the file is in our data directory
            QFile::remove(availableFileName);
            mSettings.remove("List/available");
            mSettings.remove("List/availableDate");
        }
    } else {
        // Make sure the available notation is also removed
        mSettings.remove("List/available");
        mSettings.remove("List/availableDate");
    }

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

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

    if (swFileName.isEmpty()) {
        mSettings.remove("Software/availableDate");
        return;
    }

    QFileInfo fi(swFileName);
    if (!fi.exists()) {
        mSettings.remove("Software/available");
        mSettings.remove("Software/availableDate");
        qDebug() << "Software does not yet exist.";
        return;
    }
    if (!fi.isExecutable()) {
        qWarning() << "Downloaded file: " << swFileName << " is not executable.";
        QFile::remove(swFileName);
        mSettings.remove("Software/available");
        mSettings.remove("Software/availableDate");
        return;
    }
    bin_verify_result verifyResult = verify_binary(swFileName.toUtf8().constData(),
            swFileName.toUtf8().size());
    qDebug() << "Binary verify result: " << verifyResult;
    if (verifyResult != VerifyValid) {
        qDebug() << "Failed to verify downloaded data.";
        QFile::remove(swFileName);
        mSettings.remove("Software/available");
        mSettings.remove("Software/availableDate");
        return;
    }
}

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

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

        /* Retry the download again in 10 - 20 minutes */
        QTimer::singleShot(600000 + (qrand() % 60000), this, SLOT(checkUpdates()));
    } else {
        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);
    QString filePath = QDir::toNativeSeparators(instProcInfo.absoluteFilePath());

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

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

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

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

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

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

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

    if (!ShellExecuteExW(&shExecInfo)) {
        /* Execution failed, maybe the user aborted the UAC check? */
        char* errmsg = getLastErrorMsg();
        QString qerrmsg = QString::fromUtf8(errmsg);
        free(errmsg);
        qDebug() << "Failed to start process: " << qerrmsg;
        setState(NewSoftwareAvailable);
        return;
    }
#else /* WIN32 */
    QStringList parameters;
    parameters << "/S" << "/UPDATE=1"
               << QString::fromLatin1("/D=%1").arg(installDir.path());

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

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

    closeApp();
}

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

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

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

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


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

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

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

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

    downloader->start();
}

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

void MainWindow::downloaderError(const QString &message, SSLConnection::ErrorCode error)
{
    /* TODO logging and handle error according to a plan */
    syslog_error_printf ("Failed to check for updates: %s", message.toUtf8().constData());
    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()));
}

QWidget * MainWindow::createInfoWidget()
{
    QWidget *theWidget = new QWidget;
    QVBoxLayout *infoPanelLayout = new QVBoxLayout;
    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);
    theWidget->setLayout(infoPanelLayout);

    return theWidget;
}

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

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

    /* Header 1: Update date and search button */
    mUpdatesHeader =
        new QLabel("<h2>" + tr("Certificates unchanged")+ "</h2>");
    mLastCertUpdate =
        new QLabel(tr("Installed certificates from:"));
    mLastCertUpdateContents = new QLabel(QString());
    mLastCertUpdate->hide();
    mLastCertUpdateContents->hide();
    mSoftwareVersionLabel =
        new QLabel(tr("TrustBridge Version:"));
    mSoftwareVersionContents = new QLabel(QApplication::applicationVersion());
    const QDateTime lastCheck = mSettings.value("lastUpdateCheck").toDateTime().toLocalTime();
    if (lastCheck.isValid()) {
        const QString lastUpdateCheck = QLocale::system().toString(lastCheck, DATETIME_FORMAT);
        mLastUpdateCheck =
            new QLabel(tr("Last sucessful update check:"));
        mLastUpdateCheckContents = new QLabel(lastUpdateCheck);
    } else {
        mLastUpdateCheck = new QLabel(tr("Last successful update check:"));
        mLastUpdateCheckContents = new QLabel(QString());
        mLastUpdateCheck->hide();
        mLastUpdateCheckContents->hide();
    }
    QPushButton *searchUpdates = new QPushButton(" " + tr("Check for updates"));
    searchUpdates->setIcon(QIcon(":/img/edit-find.png"));
    connect(searchUpdates, SIGNAL(clicked()), this, SLOT(checkUpdates()));

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

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

    /* Header 2: Action text and buttons */
    mUpdatesTip =
        new QLabel(tr("There are currently no changes for your certificate stores."));
    mUpdatesTip->setWordWrap(true);
    QHBoxLayout *updatesHeaderActionButtonLayout = new QHBoxLayout;
    mQuitButton = new QPushButton(" " + tr("Quit without saving"));
    mQuitButton->setIcon(QIcon(":/img/application-exit.png"));
    mQuitButton->setFixedHeight(30);

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

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

    /* The central panels. */
    QScrollArea *centralScrollArea = new QScrollArea;
    QVBoxLayout *updatesCenterLayout = new QVBoxLayout;
    QHBoxLayout *updatesNewLayout = new QHBoxLayout;
    QHBoxLayout *updatesRemoveLayout = new QHBoxLayout;
    QHBoxLayout *updatesManualLayout = new QHBoxLayout;
    mUpdatesNewCertificates =
        new QLabel("<h3>" +
            tr("Install new trusted certificates (%1/%2)").arg(0).arg(0) +
            "</h3>");
    mUpdatesDetailsNew = new QPushButton(tr("Show details"));
    connect(mUpdatesDetailsNew,
        SIGNAL(clicked()),
        this,
        SLOT(toggleUpdatesNew()));
    updatesNewLayout->addWidget(mUpdatesNewCertificates);
    updatesNewLayout->addStretch(1);
    updatesNewLayout->addWidget(mUpdatesDetailsNew);
    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(tr("Show details"));
    connect(mUpdatesDetailsRemove,
        SIGNAL(clicked()),
        this,
        SLOT(toggleUpdatesRemove()));
    updatesRemoveLayout->addWidget(mUpdatesRemoveCertificates);
    updatesRemoveLayout->addStretch(1);
    updatesRemoveLayout->addWidget(mUpdatesDetailsRemove);
    mUpdatesRemove = new CertificateListWidget(this);
    connect(mUpdatesRemove, SIGNAL(certListChanged(int)),
        this, SLOT(listChanged(int)));
    mUpdatesRemove->hide();

    mUpdatesManualCertificates =
        new QLabel("<h3>" +
            tr("Manually changed certificates (%1)").arg(0) +
            "</h3>");
    mUpdatesDetailsManual = new QPushButton(tr("Show details"));
    connect(mUpdatesDetailsManual,
        SIGNAL(clicked()),
        this,
        SLOT(toggleUpdatesManual()));
    mUpdatesDetailsManual->hide();
    mUpdatesManualCertificates->hide();
    updatesManualLayout->addWidget(mUpdatesManualCertificates);
    updatesManualLayout->addStretch(1);
    updatesManualLayout->addWidget(mUpdatesDetailsManual);
    mUpdatesManual = new CertificateListWidget(this);
    mUpdatesManual->hide();
    connect(mUpdatesManual, SIGNAL(certChanged(bool, const Certificate&)),
        this, SLOT(removeFromManual(bool, const Certificate&)));
    connect(mUpdatesManual, SIGNAL(certListChanged(int)),
        this, SLOT(listChanged(int)));

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

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

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


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

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

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

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

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

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

    installPanelLayout->addWidget(scrollArea);

    theWidget->setLayout(installPanelLayout);

    return theWidget;
}

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

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

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

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

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

    return theWidget;
}

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

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

    // The header (icon, about text)
    QImage *logoImage = new QImage(":/img/logo.png");
    QLabel *logo = new QLabel;
    logo->setBackgroundRole(QPalette::Base);
    logo->setPixmap(QPixmap::fromImage(*logoImage));
    QLabel *title = new QLabel("<h1>" + QString::fromLatin1(APPNAME) + "</h1>");
    QLabel *subTitle = new QLabel(tr("Trust in your digital communication"));
    headerTextLayout->addWidget(title);
    headerTextLayout->addWidget(subTitle);
    headerLayout->addWidget(logo);
    headerLayout->addLayout(headerTextLayout);
    headerLayout->setStretch(0, 0);
    headerLayout->setStretch(1, 10);

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

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

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

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

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

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

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

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

    /* The main pages.*/

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

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

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

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

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

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

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

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

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

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

    if (mUpdatesManual->certificates().size()) {
        mUpdatesDetailsManual->show();
        mUpdatesDetailsManual->setText(tr("Show details"));
        mUpdatesManualCertificates->show();
    } else {
        mUpdatesDetailsManual->hide();
        mUpdatesManualCertificates->hide();
        mUpdatesManual->hide();
    }

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

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

void MainWindow::loadCertificateList()
{
    /* TODO: if nothing is available (neither old nor new) add some progress
     * indication */
    mInstallList->clear();
    mRemoveList->clear();
    mUpdatesNew->clear();
    mUpdatesRemove->clear();
    QList<Certificate> newInstallCerts;
    QList<Certificate> newRemoveCerts;
    QList<Certificate> oldInstallCerts;
    QList<Certificate> oldRemoveCerts;

    if (mListToInstall.getCertificates().isEmpty()) {
        // No new list available, add old certificates.
        foreach (const Certificate &cert, mInstalledList.getCertificates()) {
            bool state = !mPreviouslyUnselected.contains(cert.base64Line());
            if (cert.isInstallCert()) {
                oldInstallCerts.append(cert);
                mInstallList->addCertificate(cert, state);
            }
            else {
                oldRemoveCerts.append(cert);
                mRemoveList->addCertificate(cert, state, !state);
            }
        }
    }
    else {
        // Sort and filter both lists.
        foreach (const Certificate &cert, mListToInstall.getCertificates()) {
            bool state = !mPreviouslyUnselected.contains(cert.base64Line());
            if (cert.isInstallCert()) {
                // Certificate with status "install".
                if (mInstalledList.getCertificates().contains(cert)) {
                    // Was in the old list.
                    oldInstallCerts.append(cert);
                    mInstallList->addCertificate(cert, state);
                }
                else {
                    // Is a brand new certificate
                    newInstallCerts.append(cert);
                    mUpdatesNew->addCertificate(cert, state, true,
                            tr("install"), tr("ignore"));
                }
            }
            else {
                // Certificate with status "remove".
                if (mInstalledList.getCertificates().contains(cert)) {
                    // Was in the old list.
                    oldRemoveCerts.append(cert);
                    // Is removed, so set editable to false.
                    mRemoveList->addCertificate(cert, state, !state);
                }
                else {
                    // Was in the old list with status "install" and now has the
                    // status "remove".
                    newRemoveCerts.append(cert);
                    mUpdatesRemove->addCertificate(cert, state);
                }
            }
        }
    }

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

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

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

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

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

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

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

    choices << mUpdatesManual->selectedCertificates();

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

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

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

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

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

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

}

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

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

void MainWindow::toggleInManual(bool state, const Certificate &cert)
{
    if (!mUpdatesManual->contains(cert)) {
        mUpdatesManual->addCertificate(cert, state);
    }
    else {
        mUpdatesManual->removeCertificate(cert);
    }

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

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

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

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

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

void MainWindow::checkAndInstallCerts()
{
    /* Checking before opening the dialog should be cheaper */
    QList<int> pids = ProcessHelp::getProcessesIdForName("firefox");
    pids.append(ProcessHelp::getProcessesIdForName("thunderbird"));

    if (pids.isEmpty()) {
        installCerts();
        return;
    }

    QStringList pNames;
    pNames << "firefox" << "thunderbird";

    ProcessWaitDialog *waitDialog = new ProcessWaitDialog(this, pNames);

    connect(waitDialog, SIGNAL(accepted()), this, SLOT(installCerts()));
    connect(waitDialog, SIGNAL(accepted()), waitDialog, SLOT(deleteLater()));

    waitDialog->exec();
    return;
}

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

void MainWindow::toggleUpdatesNew() {
    if (!mUpdatesNew->isVisible()) {
        mUpdatesDetailsNew->setText(tr("Hide details"));
        mUpdatesNew->show();
    }
    else {
        mUpdatesNew->hide();
        mUpdatesDetailsNew->setText(tr("Show details"));
    }
}

void MainWindow::toggleUpdatesRemove() {
    if (!mUpdatesRemove->isVisible()) {
        mUpdatesDetailsRemove->setText(tr("Hide details"));
        mUpdatesRemove->show();
    }
    else {
        mUpdatesRemove->hide();
        mUpdatesDetailsRemove->setText(tr("Show details"));
    }
}

void MainWindow::toggleUpdatesManual() {
    if (!mUpdatesManual->isVisible()) {
        mUpdatesDetailsManual->setText(tr("Hide details"));
        mUpdatesManual->show();
    }
    else {
        mUpdatesDetailsManual->setText(tr("Show details"));
        mUpdatesManual->hide();
    }
}

void MainWindow::closeEvent(QCloseEvent *event)
{
    if (getState() == NewListAvailable) {
        /* Only minimize to tray if there is a new list */
        QMainWindow::closeEvent(event);
        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());
    }
}

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

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

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