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

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

        /****************************************************************************
        **
        ** Copyright (C) 2005-2006 Trolltech AS. All rights reserved.
        **
        ** This file is part of the documentation of the Qt Toolkit.
        **
        ** This file may be used under the terms of the GNU General Public
        ** License version 2.0 as published by the Free Software Foundation
        ** and appearing in the file LICENSE.GPL included in the packaging of
        ** this file.  Please review the following information to ensure GNU
        ** General Public Licensing requirements will be met:
        ** http://www.trolltech.com/products/qt/opensource.html
        **
        ** If you are unsure which license is appropriate for your use, please
        ** review the following information:
        ** http://www.trolltech.com/products/qt/licensing.html or contact the
        ** sales department at sales@trolltech.com.
        **
        ** 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("assistant", 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, QMessageBox::NoButton);
            }

            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, QMessageBox::NoButton);
            }

            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, QMessageBox::NoButton);
                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 | QDir::Files)) {
                if (info.isFile() && info.isExecutable())
                    return info.absoluteFilePath();
                else if (info.isDir()) {
                    QDir currentDir(info.absoluteFilePath());
                    if (currentDir != dir && currentDir != parentDir) {
                        QString path = findExecutable(currentDir);
                        if (!path.isNull())
                            return path;
                    }
                }
            }
            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 Trolltech"), 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) {
                emit windowResized();
                inFullScreenResize = false;
            } else if (currentCategory != "[starting]") {
                resizeTimer->start(500);
            }
        }

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