|  | Home · All Classes · Main Classes · Grouped Classes · Modules · Functions |

# launcher.cpp Example File
tools/qtdemo/launcher.cpp

    /****************************************************************************
    **
    ** Copyright (C) 2005-2006 Trolltech ASA. All rights reserved.
    **
    ** This file is part of the example classes of the Qt Toolkit.
    **
    ** Licensees holding valid Qt Preview licenses may use this file in
    ** accordance with the Qt Preview License Agreement provided with the
    ** Software.
    **
    ** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
    ** information about Qt Commercial License Agreements.
    **
    ** Contact info@trolltech.com if any conditions of this licensing are
    ** not clear to you.
    **
    ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
    ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
    **
    ****************************************************************************/

    #include <QtGui>
    #include <QDomDocument>
    #include <QtAssistant/QAssistantClient>

    #include "displayshape.h"
    #include "displaywidget.h"
    #include "launcher.h"

    Launcher::Launcher(QWidget *parent)
        : QMainWindow(parent)
    {
        titleFont = font();
        titleFont.setWeight(QFont::Bold);
        buttonFont = font();
        fontRatio = 0.8;
        documentFont = font();
        inFullScreenResize = false;
        currentCategory = "[starting]";

        QAction *parentPageAction1 = new QAction(tr("Show Parent Page"), this);
        QAction *parentPageAction2 = new QAction(tr("Show Parent Page"), this);
        QAction *parentPageAction3 = new QAction(tr("Show Parent Page"), this);
        parentPageAction1->setShortcut(QKeySequence(tr("Escape")));
        parentPageAction2->setShortcut(QKeySequence(tr("Backspace")));
        parentPageAction3->setShortcut(QKeySequence(tr("Alt+Left")));

        QAction *fullScreenAction = new QAction(tr("Toggle &Full Screen"), this);
        fullScreenAction->setShortcut(QKeySequence(tr("Ctrl+F")));

        QAction *exitAction = new QAction(tr("E&xit"), this);
        exitAction->setShortcut(QKeySequence(tr("Ctrl+Q")));

        connect(parentPageAction1, SIGNAL(triggered()), this, SIGNAL(showPage()));
        connect(parentPageAction2, SIGNAL(triggered()), this, SIGNAL(showPage()));
        connect(parentPageAction3, SIGNAL(triggered()), this, SIGNAL(showPage()));
        connect(fullScreenAction, SIGNAL(triggered()),
                this, SLOT(toggleFullScreen()));
        connect(exitAction, SIGNAL(triggered()), this, SLOT(close()));

        display = new DisplayWidget;

        addAction(parentPageAction1);
        addAction(parentPageAction2);
        addAction(parentPageAction3);
        addAction(fullScreenAction);
        addAction(exitAction);

        slideshowTimer = new QTimer(this);
        slideshowTimer->setInterval(5000);
        resizeTimer = new QTimer(this);
        resizeTimer->setSingleShot(true);
        connect(resizeTimer, SIGNAL(timeout()), this, SLOT(redisplayWindow()));

        assistant = new QAssistantClient(
            QLibraryInfo::location(QLibraryInfo::BinariesPath), this);

        connect(display, SIGNAL(actionRequested(const QString &)),
                this, SLOT(executeAction(const QString &)));
        connect(display, SIGNAL(categoryRequested(const QString &)),
                this, SLOT(showExamples(const QString &)));
        connect(display, SIGNAL(documentationRequested(const QString &)),
                this, SLOT(showExampleDocumentation(const QString &)));
        connect(display, SIGNAL(exampleRequested(const QString &)),
                this, SLOT(showExampleSummary(const QString &)));

        connect(display, SIGNAL(launchRequested(const QString &)),
                this, SLOT(launchExample(const QString &)));

        connect(this, SIGNAL(showPage()), this, SLOT(showParentPage()),
                Qt::QueuedConnection);
        connect(this, SIGNAL(windowResized()), this, SLOT(redisplayWindow()),
                Qt::QueuedConnection);

        setCentralWidget(display);
        setMaximumSize(QApplication::desktop()->screenGeometry().size());
        setWindowTitle(tr("Qt Examples and Demos"));
        setWindowIcon(QPixmap(":/images/qt4-logo.png"));
    }

    bool Launcher::setup()
    {
        documentationDir = QDir(QLibraryInfo::location(
                                QLibraryInfo::DocumentationPath));

        if (!documentationDir.cd("html")) {
            // Failed to find the HTML documentation.
            // We can continue without it.
            QMessageBox::warning(this, tr("No Documentation Found"),
                tr("I could not find the Qt documentation."),
                QMessageBox::Cancel);
        }

        imagesDir = documentationDir;
        if (!imagesDir.cd("images")) {
            // Failed to find the accompanying images for the documentation.
            // We can continue without them.
            QMessageBox::warning(this, tr("No Images Found"),
                tr("I could not find any images for the Qt documentation."),
                QMessageBox::Cancel);
        }

        maximumLabels = 0;

        demosDir = QDir(QLibraryInfo::location(QLibraryInfo::DemosPath));
        int demoCategories = readInfo(":/demos.xml", demosDir);

        examplesDir = QDir(QLibraryInfo::location(QLibraryInfo::ExamplesPath));
        int exampleCategories = readInfo(":/examples.xml", examplesDir);

        if (demoCategories + exampleCategories <= 0) {
            // Failed to find the examples.
            QMessageBox::warning(this, tr("No Examples or Demos found"),
                tr("I could not find any Qt examples or demos.\n"
                   "Please ensure that Qt is installed correctly."),
                QMessageBox::Cancel);
            return false;
        }

        maximumLabels = qMax(demoCategories + exampleCategories, maximumLabels);

        // For example menus, remember that the menu has to include a Back button.
        foreach (QString category, categories)
            maximumLabels = qMax(examples[category].size() + 1, maximumLabels);

        QString mainDescription = categoryDescriptions["[main]"];
        if (!mainDescription.isEmpty())
            mainDescription += tr("\n");

        categoryDescriptions["[main]"] = mainDescription + tr(
            "<p>Press <b>Escape</b>, <b>Backspace</b>, or <b>%1</b> to "
            "return to a previous menu.</p>\n"
            "<p>Press <b>%2</b> to switch between normal and full screen "
            "modes.</p>\n"
            "<p>Use <b>%3</b> to exit the launcher.</p>").arg(QString(
                QKeySequence(tr("Alt+Left")))).arg(QString(
                QKeySequence(tr("Ctrl+F")))).arg(QString(
                QKeySequence(tr("Ctrl+Q"))));

        emit showPage();
        return true;
    }

    void Launcher::findDescriptionAndImages(const QString &uniqueName,
                                            const QString &docName)
    {
        if (documentationDir.exists(docName)) {
            exampleDetails[uniqueName]["document path"] = \
                documentationDir.absoluteFilePath(docName);

            QDomDocument exampleDoc;
            QFile exampleFile(exampleDetails[uniqueName]["document path"]);
            exampleDoc.setContent(&exampleFile);

            QDomNodeList paragraphs = exampleDoc.elementsByTagName("p");
            QString description;

            for (int p = 0; p < int(paragraphs.length()); ++p) {
                QDomNode descriptionNode = paragraphs.item(p);
                description = readExampleDescription(descriptionNode);

                if (description.indexOf(QRegExp(QString(
                    "((The|This) )?(%1 )?.*(example|demo)").arg(
                        exampleDetails[uniqueName]["name"]),
                    Qt::CaseInsensitive)) != -1) {
                    exampleDetails[uniqueName]["description"] = description;
                    break;
                }
            }

            QDomNodeList images = exampleDoc.elementsByTagName("img");
            QStringList imageFiles;

            for (int i = 0; i < int(images.length()); ++i) {
                QDomElement imageElement = images.item(i).toElement();
                QString source = imageElement.attribute("src");
                if (!source.contains("-logo"))
                    imageFiles.append(documentationDir.absoluteFilePath(source));
            }

            if (imageFiles.size() > 0)
                imagePaths[uniqueName] = imageFiles;
        }
    }

    int Launcher::readInfo(const QString &resource, const QDir &dir)
    {
        QFile categoriesFile(resource);
        QDomDocument document;
        document.setContent(&categoriesFile);
        QDomElement documentElement = document.documentElement();
        QDomNodeList categoryNodes = documentElement.elementsByTagName("category");

        readCategoryDescription(dir, "[main]");
        qtLogo.load(imagesDir.absoluteFilePath(":/images/qt4-logo.png"));
        trolltechLogo.load(imagesDir.absoluteFilePath(":/images/trolltech-logo.png"));

        for (int i = 0; i < int(categoryNodes.length()); ++i) {

            QDomNode categoryNode = categoryNodes.item(i);
            QDomElement element = categoryNode.toElement();
            QString categoryName = element.attribute("name");
            QString categoryDirName = element.attribute("dirname");
            QString categoryDocName = element.attribute("docname");
            QString categoryColor = element.attribute("color", "#f0f0f0");

            QDir categoryDir = dir;
            if (categoryDir.cd(categoryDirName)) {

                readCategoryDescription(categoryDir, categoryName);

                examples[categoryName] = QStringList();

                QDomNodeList exampleNodes = element.elementsByTagName("example");
                maximumLabels = qMax(maximumLabels, int(exampleNodes.length()));

                for (int j = 0; j < int(exampleNodes.length()); ++j) {

                    QDir exampleDir = categoryDir;

                    QDomNode exampleNode = exampleNodes.item(j);
                    element = exampleNode.toElement();
                    QString exampleName = element.attribute("name");
                    QString exampleFileName = element.attribute("filename");
                    QString exampleDirName = element.attribute("dirname");

                    examples[categoryName].append(exampleName);

                    QString uniqueName = categoryName+"-"+exampleName;
                    exampleOptions[uniqueName] = QMap<QString,QString>();
                    exampleDetails[uniqueName] = QMap<QString,QString>();

                    QString docName;
                    if (!categoryDocName.isEmpty())
                        docName = categoryDocName+"-"+exampleFileName+".html";
                    else
                        docName = categoryDirName+"-"+exampleFileName+".html";

                    exampleDetails[uniqueName]["name"] = exampleName;
                    findDescriptionAndImages(uniqueName, docName);

                    exampleOptions[uniqueName]["changedirectory"] = element.attribute("changedirectory", "true");
                    exampleOptions[uniqueName]["color"] = element.attribute("color", "#f0f0f0");

                    if (!exampleDirName.isEmpty() && !exampleDir.cd(exampleDirName))
                        continue;

                    if (element.attribute("executable", "true") != "true")
                        continue;

                    if (exampleDir.cd(exampleFileName)) {
                        QString examplePath = findExecutable(exampleDir);
                        if (!examplePath.isNull()) {
                            exampleDetails[uniqueName]["absolute path"] = exampleDir.absolutePath();
                            exampleDetails[uniqueName]["path"] = examplePath;
                        }
                    }
                }

                categories.append(categoryName);
                categoryColors[categoryName] = categoryColor;
            }
        }

        return categories.size();
    }

    QString Launcher::findExecutable(const QDir &dir) const
    {
        QDir parentDir = dir;
        parentDir.cdUp();

        QFileInfo releaseDir(dir, "release");
        if (releaseDir.isDir()) {
            QDir currentDir(releaseDir.absoluteFilePath());
            QString path = findExecutable(currentDir);
            if (!path.isEmpty())
                return path;
        }

        foreach (QFileInfo info, dir.entryInfoList(QDir::Dirs)) {
            if (info.fileName().endsWith(".app"))
                return info.absoluteFilePath();
            QDir currentDir(info.absoluteFilePath());
            if (currentDir != dir && currentDir != parentDir &&
                currentDir.dirName() != "plugins") {
                QString path = findExecutable(currentDir);
                if (!path.isNull())
                    return path;
            }
        }

        foreach (QFileInfo info, dir.entryInfoList(QDir::Files)) {
            if (info.isExecutable())
                return info.absoluteFilePath();
        }
        return QString();
    }

    void Launcher::readCategoryDescription(const QDir &categoryDir,
                                           const QString &categoryName)
    {
        if (categoryDir.exists("README")) {
            QFile file(categoryDir.absoluteFilePath("README"));
            if (!file.open(QFile::ReadOnly))
                return;

            QTextStream inputStream(&file);

            QStringList paragraphs;
            QStringList currentPara;
            bool openQuote = true;

            while (!inputStream.atEnd()) {
                QString line = inputStream.readLine();
                int at = 0;
                while ((at = line.indexOf("\"", at)) != -1) {
                    if (openQuote)
                        line[at] = QChar::Punctuation_InitialQuote;
                    else
                        line[at] = QChar::Punctuation_FinalQuote;
                    openQuote = !openQuote;
                }

                if (!line.trimmed().isEmpty()) {
                    currentPara.append(line.trimmed());
                } else if (!currentPara.isEmpty()) {
                    paragraphs.append(currentPara.join(" "));
                    currentPara.clear();
                } else
                    break;
            }

            if (!currentPara.isEmpty())
                paragraphs.append(currentPara.join(" "));

            categoryDescriptions[categoryName] = "<p>" + \
                paragraphs.join("\n</p><p>") + "</p>\n";
        }
    }

    QString Launcher::readExampleDescription(const QDomNode &parentNode) const
    {
        QString description;
        QDomNode node = parentNode.firstChild();

        while (!node.isNull()) {
            QString beginTag;
            QString endTag;
            if (node.isText())
                description += node.nodeValue();
            else if (node.hasChildNodes()) {
                if (node.nodeName() == "b") {
                    beginTag = "<b>";
                    endTag = "</b>";
                } else if (node.nodeName() == "a") {
                    beginTag = "<font color=\"blue\">";
                    endTag = "</font>";
                } else if (node.nodeName() == "i") {
                    beginTag = "<i>";
                    endTag = "</i>";
                } else if (node.nodeName() == "tt") {
                    beginTag = "<tt>";
                    endTag = "</tt>";
                }
                description += beginTag + readExampleDescription(node) + endTag;
            }

            node = node.nextSibling();
        }

        return description;
    }

    void Launcher::launchExample(const QString &uniqueName)
    {
        if (runningExamples.contains(uniqueName))
            return;

        QProcess *process = new QProcess(this);
        connect(process, SIGNAL(finished(int)), this, SLOT(enableLaunching()));

    #ifdef Q_OS_WIN
        //make sure it finds the dlls on windows
        QString curpath = QString::fromLocal8Bit(qgetenv("PATH").constData());
        QString newpath = QString("PATH=%1;%2").arg(
            QLibraryInfo::location(QLibraryInfo::BinariesPath), curpath);
        process->setEnvironment(QStringList(newpath));
    #endif

        runningExamples.append(uniqueName);
        runningProcesses[process] = uniqueName;

        if (exampleOptions[uniqueName]["changedirectory"] == "true")
            process->setWorkingDirectory(exampleDetails[uniqueName]["absolute path"]);

        process->start(exampleDetails[uniqueName]["path"]);

        if (process->state() == QProcess::Starting)
            slideshowTimer->stop();
    }

    void Launcher::enableLaunching()
    {
        QProcess *process = static_cast<QProcess*>(sender());
        QString example = runningProcesses.take(process);
        process->deleteLater();
        runningExamples.removeAll(example);

        if (example == currentExample) {
            for (int i = 0; i < display->shapesCount(); ++i) {
                DisplayShape *shape = display->shape(i);
                if (shape->metaData("launch").toString() == example) {
                    shape->setMetaData("fade", 15);
                    display->enableUpdates();
                }
            }
            slideshowTimer->start();
        }
    }

    void Launcher::executeAction(const QString &action)
    {
        if (action == "parent")
            showParentPage();
        else if (action == "exit") {
            if (runningExamples.size() == 0) {
                connect(display, SIGNAL(displayEmpty()), this, SLOT(close()));
                display->reset();
            } else
                close();
        }
    }

    void Launcher::closeEvent(QCloseEvent *event)
    {
        if (runningExamples.size() > 0) {
            if (QMessageBox::warning(this, tr("Examples Running"),
                    tr("There are examples running. Do you really want to exit?"),
                    QMessageBox::Yes|QMessageBox::No) == QMessageBox::No)
                event->ignore();
                return;
        }

        foreach (QProcess *example, runningProcesses.keys()) {
            example->terminate();
            example->waitForFinished(1000);
        }

        qDeleteAll(runningProcesses.keys());
        resizeTimer->stop();
        slideshowTimer->stop();
    }

    void Launcher::showParentPage()
    {
        slideshowTimer->stop();
        disconnect(slideshowTimer, SIGNAL(timeout()), this, 0);

        if (!currentExample.isEmpty()) {
            currentExample = "";
            redisplayWindow();
        } else if (!currentCategory.isEmpty()) {
            currentCategory = "";
            redisplayWindow();
        }
    }

    void Launcher::newPage()
    {
        slideshowTimer->stop();
        disconnect(slideshowTimer, SIGNAL(timeout()), this, 0);
        disconnect(display, SIGNAL(displayEmpty()), this, 0);
    }

    DisplayShape *Launcher::addTitle(const QString &title, qreal verticalMargin)
    {
        QPointF titlePosition = QPointF(0.0, 2*verticalMargin);

        DisplayShape *newTitle = new TitleShape(title, titleFont,
            QPen(Qt::white), titlePosition,
            QSizeF(0.5 * width(), 2*verticalMargin),
            Qt::AlignHCenter | Qt::AlignTop);

        newTitle->setPosition(QPointF(-newTitle->rect().width(), titlePosition.y()));
        newTitle->setTarget(QPointF(0.25*width(), titlePosition.y()));
        newTitle->setMetaData("fade", 15);

        display->appendShape(newTitle);

        return newTitle;
    }

    DisplayShape *Launcher::addTitleBackground(DisplayShape *titleShape)
    {
        QPainterPath backgroundPath;
        backgroundPath.addRect(0, -titleShape->rect().height()*0.3,
                               width(), titleShape->rect().height()*1.6);

        DisplayShape *titleBackground = new PanelShape(backgroundPath,
            QBrush(QColor("#a6ce39")), QBrush(QColor("#a6ce39")), Qt::NoPen,
                QPointF(width(), titleShape->position().y()),
                backgroundPath.boundingRect().size());

        titleBackground->setTarget(QPointF(0.0, titleShape->position().y()));

        display->insertShape(0, titleBackground);

        return titleBackground;
    }

    void Launcher::addVersionAndCopyright(const QRectF &rect)
    {
        DisplayShape *versionCaption = new TitleShape(
            QString("Qt %1").arg(QT_VERSION_STR), font(),
            QPen(QColor(0,0,0,0)),
            QPointF(rect.center().x(), rect.top()),
            QSizeF(0.5*rect.width(), rect.height()),
            Qt::AlignRight | Qt::AlignVCenter);

        versionCaption->setMetaData("fade", 15);
        display->appendShape(versionCaption);

        DisplayShape *copyrightCaption = new TitleShape(
            QString("Copyright \xa9 2005-2006 Trolltech ASA"), font(),
            QPen(QColor(0,0,0,0)),
            rect.topLeft(), QSizeF(0.5*rect.width(), rect.height()),
            Qt::AlignLeft | Qt::AlignVCenter);

        copyrightCaption->setMetaData("fade", 15);
        display->appendShape(copyrightCaption);
    }

    void Launcher::fadeShapes()
    {
        for (int i = 0; i < display->shapesCount(); ++i) {
            DisplayShape *shape = display->shape(i);

            shape->setMetaData("fade", -15);
            shape->setMetaData("fade minimum", 0);
        }
    }

    void Launcher::showCategories()
    {
        newPage();
        fadeShapes();
        currentCategory = "";
        currentExample = "";

        qreal horizontalMargin = 0.025*width();
        qreal verticalMargin = 0.025*height();

        DisplayShape *title = new TitleShape(tr("Qt Examples and Demos"),
            titleFont, QPen(QColor("#a6ce39")), QPointF(),
            QSizeF(0.5*width(), 4*verticalMargin));

        title->setPosition(QPointF(width()/2 - title->rect().width()/2,
                                   -title->rect().height()));
        title->setTarget(QPointF(title->position().x(), verticalMargin));

        display->appendShape(title);

        qreal topMargin = 6*verticalMargin;
        qreal bottomMargin = height() - 3.2*verticalMargin;
        qreal space = bottomMargin - topMargin;
        qreal step = qMin(title->rect().height() / fontRatio,
                          space/qreal(maximumLabels));
        qreal textHeight = fontRatio * step;

        QPointF startPosition = QPointF(0.0, topMargin);
        QSizeF maxSize(10.8*horizontalMargin, textHeight);
        qreal maxWidth = 0.0;

        QList<DisplayShape*> newShapes;

        foreach (QString category, categories) {

            DisplayShape *caption = new TitleShape(category, font(), QPen(),
                startPosition, maxSize);
            caption->setPosition(QPointF(-caption->rect().width(),
                                         caption->position().y()));
            caption->setTarget(QPointF(2*horizontalMargin, caption->position().y()));

            newShapes.append(caption);

            startPosition += QPointF(0.0, step);
            maxWidth = qMax(maxWidth, caption->rect().width());
        }

        DisplayShape *exitButton = new TitleShape(tr("Exit"), font(),
            QPen(Qt::white), startPosition, maxSize);

        exitButton->setTarget(QPointF(2*horizontalMargin, exitButton->position().y()));
        newShapes.append(exitButton);

        startPosition = QPointF(width(), topMargin);
        qreal extra = (step - textHeight)/4;

        QPainterPath backgroundPath;
        backgroundPath.addRect(-2*extra, -extra, maxWidth + 4*extra, textHeight + 2*extra);

        foreach (QString category, categories) {

            DisplayShape *background = new PanelShape(backgroundPath,
                QBrush(categoryColors[category]), QBrush(QColor("#e0e0ff")),
                Qt::NoPen, startPosition,
                QSizeF(maxWidth + 4*extra, textHeight + 2*extra));

            background->setMetaData("category", category);
            background->setInteractive(true);
            background->setTarget(QPointF(2*horizontalMargin,
                                          background->position().y()));
            display->insertShape(0, background);
            startPosition += QPointF(0.0, step);
        }

        QPainterPath exitPath;
        exitPath.moveTo(-2*extra, -extra);
        exitPath.lineTo(-8*extra, textHeight/2);
        exitPath.lineTo(-extra, textHeight + extra);
        exitPath.lineTo(maxWidth + 2*extra, textHeight + extra);
        exitPath.lineTo(maxWidth + 2*extra, -extra);
        exitPath.closeSubpath();

        DisplayShape *exitBackground = new PanelShape(exitPath,
            QBrush(QColor("#a6ce39")), QBrush(QColor("#c7f745")), Qt::NoPen,
            startPosition, QSizeF(maxWidth + 10*extra, textHeight + 2*extra));

        exitBackground->setMetaData("action", "exit");
        exitBackground->setInteractive(true);
        exitBackground->setTarget(QPointF(2*horizontalMargin,
                                          exitBackground->position().y()));
        display->insertShape(0, exitBackground);

        foreach (DisplayShape *caption, newShapes) {
            QPointF position = caption->target();
            QSizeF size = caption->rect().size();
            caption->setPosition(QPointF(-maxWidth, position.y()));
            display->appendShape(caption);
        }

        qreal leftMargin = 3*horizontalMargin + maxWidth;
        qreal rightMargin = width() - 3*horizontalMargin;

        DocumentShape *description = new DocumentShape(categoryDescriptions["[main]"],
            documentFont, QPointF(leftMargin, topMargin),
            QSizeF(rightMargin - leftMargin, space), 0);

        description->setMetaData("fade", 10);
        display->appendShape(description);

        qreal imageHeight = title->rect().height() + verticalMargin;

        qreal qtLength = qMin(imageHeight, title->rect().left()-3*horizontalMargin);
        QSizeF qtMaxSize = QSizeF(qtLength, qtLength);

        DisplayShape *qtShape = new ImageShape(qtLogo,
            QPointF(2*horizontalMargin-extra, -imageHeight), qtMaxSize, 0,
            Qt::AlignLeft | Qt::AlignVCenter);

        qtShape->setMetaData("fade", 15);
        qtShape->setTarget(QPointF(qtShape->rect().x(), verticalMargin));
        display->insertShape(0, qtShape);

        QSizeF trolltechMaxSize = QSizeF(
            width()-3*horizontalMargin-title->rect().right(), imageHeight);

        DisplayShape *trolltechShape = new ImageShape(trolltechLogo,
            QPointF(width()-2*horizontalMargin-trolltechMaxSize.width()+extra,
                    -imageHeight),
            trolltechMaxSize, 0, Qt::AlignRight | Qt::AlignVCenter);

        trolltechShape->setMetaData("fade", 15);
        trolltechShape->setTarget(QPointF(trolltechShape->rect().x(),
                                          verticalMargin));
        display->insertShape(0, trolltechShape);

        addVersionAndCopyright(QRectF(2*horizontalMargin,
                               height() - verticalMargin - textHeight,
                               width()-4*horizontalMargin, textHeight));
    }

    void Launcher::showExamples(const QString &category)
    {
        newPage();
        fadeShapes();
        currentCategory = category;
        currentExample = "";

        qreal horizontalMargin = 0.025*width();
        qreal verticalMargin = 0.025*height();

        DisplayShape *newTitle = addTitle(category, verticalMargin);
        addTitleBackground(newTitle);

        qreal topMargin = 6*verticalMargin;
        qreal bottomMargin = height() - 3.2*verticalMargin;
        qreal space = bottomMargin - topMargin;
        qreal step = qMin(newTitle->rect().height() / fontRatio,
                          space/qreal(maximumLabels));
        qreal textHeight = fontRatio * step;

        QPointF startPosition = QPointF(2*horizontalMargin, height() + topMargin);
        QPointF finishPosition = QPointF(2*horizontalMargin, topMargin);
        QSizeF maxSize(32*horizontalMargin, textHeight);
        qreal maxWidth = 0.0;

        foreach (QString example, examples[currentCategory]) {

            DisplayShape *caption = new TitleShape(example, font(), QPen(),
                startPosition, maxSize);
            caption->setTarget(finishPosition);

            display->appendShape(caption);

            startPosition += QPointF(0.0, step);
            finishPosition += QPointF(0.0, step);
            maxWidth = qMax(maxWidth, caption->rect().width());
        }

        DisplayShape *menuButton = new TitleShape(tr("Main Menu"), font(),
            QPen(Qt::white), startPosition, maxSize);
        menuButton->setTarget(finishPosition);

        display->appendShape(menuButton);
        maxWidth = qMax(maxWidth, menuButton->rect().width());

        startPosition = QPointF(width(), topMargin);
        qreal extra = (step - textHeight)/4;

        foreach (QString example, examples[currentCategory]) {

            QString uniqueName = currentCategory+"-"+example;
            QPainterPath path;
            path.addRect(-2*extra, -extra, maxWidth + 4*extra, textHeight + 2*extra);

            DisplayShape *background = new PanelShape(path,
                QBrush(QColor(exampleOptions[uniqueName]["color"])),
                QBrush(QColor("#e0e0ff")),
                Qt::NoPen, startPosition,
                QSizeF(maxWidth + 4*extra, textHeight + 2*extra));

            background->setMetaData("example", uniqueName);
            background->setInteractive(true);
            background->setTarget(QPointF(2*horizontalMargin,
                                          background->position().y()));
            display->insertShape(0, background);
            startPosition += QPointF(0.0, step);
        }

        QPainterPath backPath;
        backPath.moveTo(-2*extra, -extra);
        backPath.lineTo(-8*extra, textHeight/2);
        backPath.lineTo(-extra, textHeight + extra);
        backPath.lineTo(maxWidth + 2*extra, textHeight + extra);
        backPath.lineTo(maxWidth + 2*extra, -extra);
        backPath.closeSubpath();

        DisplayShape *buttonBackground = new PanelShape(backPath,
            QBrush(QColor("#a6ce39")), QBrush(QColor("#c7f745")), Qt::NoPen,
            startPosition, QSizeF(maxWidth + 10*extra, textHeight + 2*extra));

        buttonBackground->setMetaData("action", "parent");
        buttonBackground->setInteractive(true);
        buttonBackground->setTarget(QPointF(2*horizontalMargin,
                                            buttonBackground->position().y()));
        display->insertShape(0, buttonBackground);

        qreal leftMargin = 3*horizontalMargin + maxWidth;
        qreal rightMargin = width() - 3*horizontalMargin;

        DocumentShape *description = new DocumentShape(
            categoryDescriptions[currentCategory], documentFont,
            QPointF(leftMargin, topMargin), QSizeF(rightMargin - leftMargin, space),
            0);

        description->setMetaData("fade", 10);

        display->appendShape(description);

        addVersionAndCopyright(QRectF(2*horizontalMargin,
                               height() - verticalMargin - textHeight,
                               width()-4*horizontalMargin, textHeight));
    }

    void Launcher::showExampleDocumentation(const QString &uniqueName)
    {
        disconnect(display, SIGNAL(displayEmpty()), this, 0);
        currentExample = uniqueName;

        assistant->showPage(exampleDetails[uniqueName]["document path"]);
    }

    void Launcher::showExampleSummary(const QString &uniqueName)
    {
        newPage();
        fadeShapes();
        currentExample = uniqueName;

        qreal horizontalMargin = 0.025*width();
        qreal verticalMargin = 0.025*height();

        DisplayShape *newTitle = addTitle(exampleDetails[currentExample]["name"],
                                          verticalMargin);
        DisplayShape *titleBackground = addTitleBackground(newTitle);

        qreal topMargin = 2*verticalMargin + titleBackground->rect().bottom();
        qreal bottomMargin = height() - 8*verticalMargin;
        qreal space = bottomMargin - topMargin;
        qreal step = qMin(newTitle->rect().height() / fontRatio,
                          (bottomMargin + qreal(4.8)*verticalMargin - topMargin)
                          /qreal(maximumLabels));
        qreal textHeight = fontRatio * step;

        qreal leftMargin = 3*horizontalMargin;
        qreal rightMargin = width() - 3*horizontalMargin;

        if (exampleDetails[currentExample].contains("description")) {
            DocumentShape *description = new DocumentShape(
                exampleDetails[currentExample]["description"], documentFont,
                QPointF(leftMargin, topMargin),
                QSizeF(rightMargin-leftMargin, space), 0);

            description->setMetaData("fade", 10);

            description->setPosition(QPointF(description->position().x(),
                0.8*height() - description->rect().height()));

            display->appendShape(description);
            space = description->position().y() - topMargin - 2*verticalMargin;
        }

        if (imagePaths.contains(currentExample)) {

            QImage image(imagePaths[currentExample][0]);

            QSizeF imageMaxSize = QSizeF(width() - 8*horizontalMargin, space);

            currentFrame = new ImageShape(image,
                QPointF(width() - imageMaxSize.width()/2, topMargin),
                imageMaxSize);

            currentFrame->setMetaData("fade", 15);
            currentFrame->setTarget(QPointF(width()/2 - imageMaxSize.width()/2,
                                            topMargin));

            display->appendShape(currentFrame);

            if (imagePaths[currentExample].size() > 1) {
                connect(slideshowTimer, SIGNAL(timeout()),
                        this, SLOT(updateExampleSummary()));

                slideshowFrame = 0;
                slideshowTimer->start();
            }
        }

        QSizeF maxSize(0.3 * width(), 2*verticalMargin);
        leftMargin = 0.0;
        rightMargin = 0.0;

        if (true) {
            DisplayShape *backButton = new TitleShape(currentCategory, font(),
                QPen(Qt::white), QPointF(0.1*width(), height()), maxSize,
                Qt::AlignLeft | Qt::AlignTop);
            backButton->setTarget(QPointF(backButton->position().x(),
                                          height() - 5.2*verticalMargin));

            display->appendShape(backButton);

            qreal maxWidth = backButton->rect().width();
            qreal textHeight = backButton->rect().height();
            qreal extra = (3*verticalMargin - textHeight)/4;

            QPainterPath path;
            path.moveTo(-extra, -extra);
            path.lineTo(-4*extra, textHeight/2);
            path.lineTo(-extra, textHeight + extra);
            path.lineTo(maxWidth + 2*extra, textHeight + extra);
            path.lineTo(maxWidth + 2*extra, -extra);
            path.closeSubpath();

            DisplayShape *buttonBackground = new PanelShape(path,
                QBrush(QColor("#a6ce39")), QBrush(QColor("#c7f745")), Qt::NoPen,
                backButton->position(),
                QSizeF(maxWidth + 6*extra, textHeight + 2*extra));

            buttonBackground->setMetaData("category", currentCategory);
            buttonBackground->setInteractive(true);
            buttonBackground->setTarget(backButton->target());

            display->insertShape(0, buttonBackground);

            leftMargin = buttonBackground->rect().right();
        }

        if (exampleDetails[currentExample].contains("absolute path")) {

            DisplayShape *launchCaption = new TitleShape(tr("Launch"),
                font(), QPen(Qt::white), QPointF(0.0, 0.0), maxSize,
                Qt::AlignLeft | Qt::AlignTop);
            launchCaption->setPosition(QPointF(
                0.9*width() - launchCaption->rect().width(), height()));
            launchCaption->setTarget(QPointF(launchCaption->position().x(),
                                             height() - 5.2*verticalMargin));

            display->appendShape(launchCaption);

            qreal maxWidth = launchCaption->rect().width();
            qreal textHeight = launchCaption->rect().height();
            qreal extra = (3*verticalMargin - textHeight)/4;

            QPainterPath path;
            path.addRect(-2*extra, -extra, maxWidth + 4*extra, textHeight + 2*extra);

            QColor backgroundColor = QColor("#a63e39");
            QColor highlightedColor = QColor("#f95e56");

            DisplayShape *background = new PanelShape(path,
                QBrush(backgroundColor), QBrush(highlightedColor), Qt::NoPen,
                launchCaption->position(),
                QSizeF(maxWidth + 4*extra, textHeight + 2*extra));

            background->setMetaData("fade minimum", 120);
            background->setMetaData("launch", currentExample);
            background->setInteractive(true);
            background->setTarget(launchCaption->target());

            if (runningExamples.contains(currentExample)) {
                background->setMetaData("highlight", true);
                background->setMetaData("highlight scale", 0.99);
                background->animate();
                background->setMetaData("fade", -135);
                slideshowTimer->stop();
            }

            display->insertShape(0, background);

            rightMargin = background->rect().left();
        }

        if (exampleDetails[currentExample].contains("document path")) {

            DisplayShape *documentCaption = new TitleShape(tr("Show Documentation"),
                font(), QPen(Qt::white), QPointF(0.0, 0.0), maxSize,
                Qt::AlignLeft | Qt::AlignTop);

            if (rightMargin == 0.0) {
                documentCaption->setPosition(QPointF(
                    0.9*width() - documentCaption->rect().width(), height()));
            } else {
                documentCaption->setPosition(QPointF(
                    leftMargin/2 + rightMargin/2 - documentCaption->rect().width()/2,
                    height()));
            }
            documentCaption->setTarget(QPointF(documentCaption->position().x(),
                                               height() - 5.2*verticalMargin));

            display->appendShape(documentCaption);

            qreal maxWidth = documentCaption->rect().width();
            qreal textHeight = documentCaption->rect().height();
            qreal extra = (3*verticalMargin - textHeight)/4;

            QPainterPath path;
            path.addRect(-2*extra, -extra, maxWidth + 4*extra, textHeight + 2*extra);

            DisplayShape *background = new PanelShape(path,
                QBrush(QColor("#9c9cff")), QBrush(QColor("#cfcfff")), Qt::NoPen,
                documentCaption->position(),
                QSizeF(maxWidth + 4*extra, textHeight + 2*extra));

            background->setMetaData("fade minimum", 120);
            background->setMetaData("documentation", currentExample);
            background->setInteractive(true);
            background->setTarget(documentCaption->target());

            display->insertShape(0, background);
        }

        addVersionAndCopyright(QRectF(2*horizontalMargin,
                               height() - verticalMargin - textHeight,
                               width()-4*horizontalMargin, textHeight));
    }

    void Launcher::updateExampleSummary()
    {
        if (imagePaths.contains(currentExample)) {

            currentFrame->setMetaData("fade", -15);
            currentFrame->setTarget(currentFrame->position() - QPointF(0.5*width(),
                                    0));

            slideshowFrame = (slideshowFrame+1) % imagePaths[currentExample].size();
            QImage image(imagePaths[currentExample][slideshowFrame]);

            QSizeF imageSize = currentFrame->size();
            QPointF imagePosition = QPointF(width() - imageSize.width()/2,
                                            currentFrame->position().y());

            currentFrame = new ImageShape(image, imagePosition, imageSize);
            currentFrame->setMetaData("fade", 15);
            currentFrame->setTarget(QPointF(width()/2 - imageSize.width()/2,
                                            imagePosition.y()));

            display->appendShape(currentFrame);
        }
    }

    void Launcher::toggleFullScreen()
    {
        if (inFullScreenResize)
            return;

        inFullScreenResize = true;

        connect(display, SIGNAL(displayEmpty()), this, SLOT(resizeWindow()),
                Qt::QueuedConnection);
        display->reset();
    }

    void Launcher::resizeWindow()
    {
        disconnect(display, SIGNAL(displayEmpty()), this, 0);

        if (isFullScreen())
            showNormal();
        else
            showFullScreen();
    }

    void Launcher::resizeEvent(QResizeEvent *event)
    {
        Q_UNUSED(event);

        documentFont = font();
        documentFont.setPointSizeF(qMin(documentFont.pointSizeF()*width()/640.0,
                                        documentFont.pointSizeF()*height()/480.0));

        if (inFullScreenResize)
            inFullScreenResize = false;

        if (currentCategory != "[starting]")
            resizeTimer->start(500);
    }

    void Launcher::redisplayWindow()
    {
        if (!currentExample.isEmpty())
            showExampleSummary(currentExample);
        else if (!currentCategory.isEmpty())
            showExamples(currentCategory);
        else
            showCategories();
    }