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

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

     /****************************************************************************
     **
     ** Copyright (C) 2005-2007 Trolltech ASA. All rights reserved.
     **
     ** This file is part of the example classes 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(
             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-2007 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();
     }