#############################################################################
##
## Copyright (C) 2010 Riverbank Computing Limited.
## Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
## All rights reserved.
##
## This file is part of the examples of PyQt.
##
## $QT_BEGIN_LICENSE:LGPL$
## Commercial Usage
## Licensees holding valid Qt Commercial licenses may use this file in
## accordance with the Qt Commercial License Agreement provided with the
## Software or, alternatively, in accordance with the terms contained in
## a written agreement between you and Nokia.
##
## GNU Lesser General Public License Usage
## Alternatively, this file may be used under the terms of the GNU Lesser
## General Public License version 2.1 as published by the Free Software
## Foundation and appearing in the file LICENSE.LGPL included in the
## packaging of this file.  Please review the following information to
## ensure the GNU Lesser General Public License version 2.1 requirements
## will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
##
## In addition, as a special exception, Nokia gives you certain additional
## rights.  These rights are described in the Nokia Qt LGPL Exception
## version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
##
## GNU General Public License Usage
## Alternatively, this file may be used under the terms of the GNU
## General Public License version 3.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 the GNU General Public License version 3.0 requirements will be
## met: http://www.gnu.org/copyleft/gpl.html.
##
## If you have questions regarding the use of this file, please contact
## Nokia at qt-info@nokia.com.
## $QT_END_LICENSE$
##
#############################################################################


import os
import sys

from PyQt4 import QtCore, QtGui, QtXml

from colors import Colors
from demoitemanimation import DemoItemAnimation
from examplecontent import ExampleContent
from itemcircleanimation import ItemCircleAnimation
from menucontent import MenuContentItem
from score import Score
from textbutton import TextButton


class MenuManager(QtCore.QObject):
    ROOT, MENU1, MENU2, LAUNCH, DOCUMENTATION, QUIT, FULLSCREEN, UP, DOWN, \
            BACK, LAUNCH_QML = range(11)

    pInstance = None

    def __init__(self):
        super(MenuManager, self).__init__()

        self.contentsDoc = None
        self.assistantProcess = QtCore.QProcess()
        self.helpRootUrl = ''
        self.docDir = QtCore.QDir()
        self.imgDir = QtCore.QDir()

        self.info = {}
        self.window = None
        self.qmlRoot = None
        self.declarativeEngine = None

        self.ticker = None
        self.tickerInAnim = None
        self.upButton = None
        self.downButton = None
        self.helpEngine = None
        self.score = Score()
        self.currentMenu = "[no menu visible]"
        self.currentCategory = "[no category visible]"
        self.currentMenuButtons = "[no menu buttons visible]"
        self.currentInfo = "[no info visible]"
        self.currentMenuCode = -1
        self.readXmlDocument()
        self.initHelpEngine()

    @classmethod
    def instance(cls):
        if cls.pInstance is None:
            cls.pInstance = cls()

        return cls.pInstance

    def getResource(self, name):
        if self.helpEngine is None:
            return QtCore.QByteArray()

        ba = self.helpEngine.fileData(QtCore.QUrl(name))

        if ba.isEmpty():
            Colors.debug(" - WARNING: Could not get", name)

        return ba

    def readXmlDocument(self):
        self.contentsDoc = QtXml.QDomDocument()

        xml_file = QtCore.QFile(':/xml/examples.xml')
        statusOK, errorStr, errorLine, errorColumn = \
                self.contentsDoc.setContent(xml_file, True)

        if not statusOK:
            QtGui.QMessageBox.critical(None, "DOM Parser",
                    "Could not read or find the contents document. Error at "
                    "line %d, column %d:\n%s" % (errorLine, errorColumn, errorStr))
            sys.exit(-1)

    def initHelpEngine(self):
        # Qt5's QtHelp crashes on exit because of an apparent bug in the tidy
        # up of (non-existent) database connections in QtSql.
        if QtCore.qVersion().startswith('5'):
            return

        from PyQt4 import QtHelp

        self.helpRootUrl = 'qthelp://com.trolltech.qt.%d%d%d/qdoc/' % (QtCore.QT_VERSION >> 16, (QtCore.QT_VERSION >> 8) & 0xff, QtCore.QT_VERSION & 0xff)

        # Store help collection file in cache dir of assistant.
        cacheDir = QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.DataLocation) + '/Trolltech/Assistant/'
        helpDataFile = 'qtdemo_%s.qhc' % QtCore.QT_VERSION_STR

        dir = QtCore.QDir()
        if not dir.exists(cacheDir):
            dir.mkpath(cacheDir)

        # Create help engine (and new helpDataFile if it does not exist).
        self.helpEngine = QtHelp.QHelpEngineCore(cacheDir + helpDataFile)
        self.helpEngine.setupData()

        qtDocRoot = QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.DocumentationPath) + '/qch'
        qtDocRoot = QtCore.QDir(qtDocRoot).absolutePath()

        qchFiles = ['/qt.qch', '/designer.qch', '/linguist.qch']

        oldDir = self.helpEngine.customValue('docDir', '')
        if oldDir != qtDocRoot:
            for qchFile in qchFiles:
                self.helpEngine.unregisterDocumentation(QtHelp.QHelpEngineCore.namespaceName(qtDocRoot + qchFile))

        # If the data that the engine will work on is not yet registered, do it
        # now.
        for qchFile in qchFiles:
            self.helpEngine.registerDocumentation(qtDocRoot + qchFile)

        self.helpEngine.setCustomValue('docDir', qtDocRoot)

    def itemSelected(self, userCode, menuName):
        if userCode == MenuManager.LAUNCH:
            self.launchExample(self.currentInfo)
        elif userCode == MenuManager.LAUNCH_QML:
            self.launchQmlExample(self.currentInfo)
        elif userCode == MenuManager.DOCUMENTATION:
            self.showDocInAssistant(self.currentInfo)
        elif userCode == MenuManager.QUIT:
            self.window.quit()
        elif userCode == MenuManager.FULLSCREEN:
            self.window.toggleFullscreen()
        elif userCode == MenuManager.ROOT:
            # Out.
            self.score.queueMovie(self.currentMenu + ' -out', Score.FROM_START,
                    Score.LOCK_ITEMS)
            self.score.queueMovie(self.currentMenuButtons + ' -out',
                    Score.FROM_START, Score.LOCK_ITEMS)
            self.score.queueMovie(self.currentInfo + ' -out')
            self.score.queueMovie(self.currentInfo + ' -buttons -out',
                    Score.NEW_ANIMATION_ONLY)
            self.score.queueMovie('back -out', Score.ONLY_IF_VISIBLE)

            if self.qmlRoot is not None:
                self.qmlRoot.setProperty('show', False)

            # Book-keeping.
            self.currentMenuCode = MenuManager.ROOT
            self.currentMenu = menuName + ' -menu1'
            self.currentMenuButtons = menuName + ' -buttons'
            self.currentInfo = menuName + ' -info'

            # In.
            self.score.queueMovie('upndown -shake')
            self.score.queueMovie(self.currentMenu, Score.FROM_START,
                    Score.UNLOCK_ITEMS)
            self.score.queueMovie(self.currentMenuButtons, Score.FROM_START,
                    Score.UNLOCK_ITEMS)
            self.score.queueMovie(self.currentInfo)

            if not Colors.noTicker:
                self.ticker.doIntroTransitions = True
                self.tickerInAnim.startDelay = 2000
                self.ticker.useGuideQt()
                self.score.queueMovie('ticker', Score.NEW_ANIMATION_ONLY)
        elif userCode == MenuManager.MENU1:
            # Out.
            self.score.queueMovie(self.currentMenu + ' -out', Score.FROM_START,
                    Score.LOCK_ITEMS)
            self.score.queueMovie(self.currentMenuButtons + ' -out',
                    Score.FROM_START, Score.LOCK_ITEMS)
            self.score.queueMovie(self.currentInfo + ' -out')

            if self.qmlRoot is not None:
                self.qmlRoot.setProperty('show', False)

            # Book-keeping.
            self.currentMenuCode = MenuManager.MENU1
            self.currentCategory = menuName
            self.currentMenu = menuName + ' -menu1'
            self.currentInfo = menuName + ' -info'

            # In.
            self.score.queueMovie('upndown -shake')
            self.score.queueMovie('back -in')
            self.score.queueMovie(self.currentMenu, Score.FROM_START,
                    Score.UNLOCK_ITEMS)
            self.score.queueMovie(self.currentInfo)

            if not Colors.noTicker:
                self.ticker.useGuideTt()
        elif userCode == MenuManager.MENU2:
            # Out.
            self.score.queueMovie(self.currentInfo + ' -out',
                    Score.NEW_ANIMATION_ONLY)
            self.score.queueMovie(self.currentInfo + ' -buttons -out',
                    Score.NEW_ANIMATION_ONLY)

            if self.qmlRoot is not None:
                self.qmlRoot.setProperty('show', False)

            # Book-keeping.
            self.currentMenuCode = MenuManager.MENU2
            self.currentInfo = menuName

            # In/shake.
            self.score.queueMovie('upndown -shake')
            self.score.queueMovie('back -shake')
            self.score.queueMovie(self.currentMenu + ' -shake')
            self.score.queueMovie(self.currentInfo, Score.NEW_ANIMATION_ONLY)
            self.score.queueMovie(self.currentInfo + ' -buttons',
                    Score.NEW_ANIMATION_ONLY)

            if not Colors.noTicker:
                self.score.queueMovie('ticker -out', Score.NEW_ANIMATION_ONLY)
        elif userCode == MenuManager.UP:
            backMenu = self.info[self.currentMenu]['back']
            if backMenu:
                self.score.queueMovie(self.currentMenu + ' -top_out',
                        Score.FROM_START, Score.LOCK_ITEMS)
                self.score.queueMovie(backMenu + ' -bottom_in',
                        Score.FROM_START, Score.UNLOCK_ITEMS)
                self.currentMenu = backMenu
        elif userCode == MenuManager.DOWN:
            moreMenu = self.info[self.currentMenu]['more']
            if moreMenu:
                self.score.queueMovie(self.currentMenu + ' -bottom_out',
                        Score.FROM_START, Score.LOCK_ITEMS)
                self.score.queueMovie(moreMenu + ' -top_in', Score.FROM_START,
                        Score.UNLOCK_ITEMS)
                self.currentMenu = moreMenu
        elif userCode == MenuManager.BACK:
            if self.currentMenuCode == MenuManager.MENU2:
                # Out.
                self.score.queueMovie(self.currentInfo + ' -out',
                        Score.NEW_ANIMATION_ONLY)
                self.score.queueMovie(self.currentInfo + ' -buttons -out',
                        Score.NEW_ANIMATION_ONLY)

                if self.qmlRoot is not None:
                    self.qmlRoot.setProperty('show', False)

                # Book-keeping.
                self.currentMenuCode = MenuManager.MENU1
                self.currentMenuButtons = self.currentCategory + ' -buttons'
                self.currentInfo = self.currentCategory + ' -info'

                # In/shake.
                self.score.queueMovie('upndown -shake')
                self.score.queueMovie(self.currentMenu + ' -shake')
                self.score.queueMovie(self.currentInfo,
                        Score.NEW_ANIMATION_ONLY)
                self.score.queueMovie(self.currentInfo + ' -buttons',
                        Score.NEW_ANIMATION_ONLY)

                if not Colors.noTicker:
                    self.ticker.doIntroTransitions = False
                    self.tickerInAnim.startDelay = 500
                    self.score.queueMovie('ticker', Score.NEW_ANIMATION_ONLY)
            elif self.currentMenuCode != MenuManager.ROOT:
                self.itemSelected(MenuManager.ROOT, Colors.rootMenuName)

        # Update back and more buttons.
        if self.info.setdefault(self.currentMenu, {}).get('back'):
            back_state = TextButton.OFF
        else:
            back_state = TextButton.DISABLED

        if self.info[self.currentMenu].get('more'):
            more_state = TextButton.OFF
        else:
            more_state = TextButton.DISABLED

        self.upButton.setState(back_state)
        self.downButton.setState(more_state)

        if self.score.hasQueuedMovies():
            self.score.playQue()
            # Playing new movies might include loading etc., so ignore the FPS
            # at this point.
            self.window.fpsHistory = []

    def showDocInAssistant(self, name):
        url = self.resolveDocUrl(name)
        Colors.debug("Sending URL to Assistant:", url)

        # Start assistant if it's not already running.
        if self.assistantProcess.state() != QtCore.QProcess.Running:
            app = QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.BinariesPath) + QtCore.QDir.separator()

            if sys.platform == 'darwin':
                app += 'Assistant.app/Contents/MacOS/Assistant'
            else:
                app += 'assistant'

            args = ['-enableRemoteControl']
            self.assistantProcess.start(app, args)
            if not self.assistantProcess.waitForStarted():
                QtGui.QMessageBox.critical(None, "PyQt Demo",
                        "Could not start %s." % app)
                return

        # Send command through remote control even if the process was just
        # started to activate assistant and bring it to the front.
        cmd_str = QtCore.QTextStream(self.assistantProcess)
        cmd_str << 'SetSource ' << url << '\n'

    def launchExample(self, name):
        executable = self.resolveExeFile(name)

        process = QtCore.QProcess(self)
        process.finished.connect(self.exampleFinished)
        process.error.connect(self.exampleError)

        if sys.platform == 'win32':
            # Make sure it finds the DLLs on Windows.
            curpath = os.getenv('PATH')
            newpath = 'PATH=%s;%s' % (QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.BinariesPath), curpath)
            process.setEnvironment([newpath])

        if self.info[name]['changedirectory'] != 'false':
            workingDirectory = self.resolveDataDir(name)
            process.setWorkingDirectory(workingDirectory)
            Colors.debug("Setting working directory:", workingDirectory)

        Colors.debug("Launching:", executable)
        process.start(sys.executable, [executable])

    def launchQmlExample(self, name):
        if self.declarativeEngine is None:
            QtGui.QMessageBox.critical(None, "QtDeclarative unsupported",
                    "The QtDeclarative module is not supported by this "
                    "installation of PyQt.",
                    QtGui.QMessageBox.Cancel)
            return

        if self.qmlRoot is None:
            self.exampleError(QtCore.QProcess.UnknownError)
            return

        qml_dir = self.resolveDataDir(name)
        qml_file = QtCore.QFile(qml_dir + '/main.qml')

        if not qml_file.exists():
            qml_file.setFileName(qml_dir + '/' + qml_dir.split('/')[-1] + '.qml')
            if not qml_file.exists():
                self.exampleError(QtCore.QProcess.UnknownError)
                return

        # Unload any existing component.
        self.qmlRoot.setProperty('qmlFile', '')
        self.qmlRoot.setProperty('show', True)
        self.qmlRoot.setProperty('qmlFile', QtCore.QUrl.fromLocalFile(qml_file.fileName()))

    def exampleFinished(self):
        pass

    def exampleError(self, error):
        if error != QtCore.QProcess.Crashed:
            QtGui.QMessageBox.critical(None, "Failed to launch the example",
                    "Could not launch the example. Ensure that it has been "
                    "built.",
                    QtGui.QMessageBox.Cancel)

    def init(self, window):
        self.window = window

        # Create div.
        self.createTicker()
        self.createUpnDownButtons()
        self.createBackButton()

        # Create first level menu.
        rootElement = self.contentsDoc.documentElement()
        self.createRootMenu(rootElement)

        # Create second level menus.
        level2MenuNode = rootElement.firstChild()
        while not level2MenuNode.isNull():
            level2MenuElement = level2MenuNode.toElement()
            self.createSubMenu(level2MenuElement)

            # Create leaf menu and example info.
            exampleNode = level2MenuElement.firstChild()
            while not exampleNode.isNull():
                exampleElement = exampleNode.toElement()
                self.readInfoAboutExample(exampleElement)
                self.createLeafMenu(exampleElement)
                exampleNode = exampleNode.nextSibling()

            level2MenuNode = level2MenuNode.nextSibling()

        # Support QML if possible.
        try:
            from PyQt4 import QtDeclarative
        except ImportError:
            return

        self.declarativeEngine = QtDeclarative.QDeclarativeEngine(self)
        self.declarativeEngine.quit.connect(self.quitQML)

        component = QtDeclarative.QDeclarativeComponent(self.declarativeEngine,
                QtCore.QUrl('qrc:/qml/qmlShell.qml'), self)
        if component.isReady():
            self.qmlRoot = component.create()

        if self.qmlRoot is not None:
            self.qmlRoot.setHeight(window.scene.sceneRect().height())
            self.qmlRoot.setWidth(window.scene.sceneRect().width())
            self.qmlRoot.setZValue(101)
            self.qmlRoot.setCursor(QtCore.Qt.ArrowCursor)
            window.scene.addItem(self.qmlRoot)

            window.viewport().setFocusPolicy(QtCore.Qt.NoFocus)
            window.setFocusPolicy(QtCore.Qt.StrongFocus)
            window.scene.setStickyFocus(True)
            window.setFocus()
        else:
            Colors.debug("Error initializing QML subsystem, Declarative examples will not work")

    def quitQML(self):
        if self.qmlRoot is not None:
            self.qmlRoot.setProperty('show', False)

    def readInfoAboutExample(self, example):
        name = str(example.attribute('name'))
        if name in self.info:
            Colors.debug("__WARNING: MenuManager.readInfoAboutExample: "
                         "Demo/example with name", name, "appears twice in "
                         "the xml-file!__")

        self.info.setdefault(name, {})['filename'] = str(example.attribute('filename'))
        self.info[name]['category'] = str(example.parentNode().toElement().tagName())
        self.info[name]['dirname'] = str(example.parentNode().toElement().attribute('dirname'))
        self.info[name]['changedirectory'] = str(example.attribute('changedirectory'))
        self.info[name]['image'] = str(example.attribute('image'))
        self.info[name]['qml'] = str(example.attribute('qml'))

    def resolveDir(self, name):
        dirName = self.info[name]['dirname']
        category = self.info[name]['category']
        fileName = self.info[name]['filename'].split('/')

        dir = QtCore.QFileInfo(__file__).dir()
        # To the 'demos' directory.
        dir.cdUp()
        # To the 'examples' directory.
        dir.cdUp()

        dir.cd(dirName)

        if len(fileName) > 1:
            dir.cd('/'.join(fileName[:-1]))

        # This may legitimately fail if the example is just a simple .py file.
        dir.cd(fileName[-1])

        return dir

    def resolveDataDir(self, name):
        return self.resolveDir(name).absolutePath()

    def resolveExeFile(self, name):
        dir = self.resolveDir(name)

        fileName = self.info[name]['filename'].split('/')[-1]

        pyFile = QtCore.QFile(dir.path() + '/' + fileName + '.py')
        if pyFile.exists():
            return pyFile.fileName()

        pywFile = QtCore.QFile(dir.path() + '/' + fileName + '.pyw')
        if pywFile.exists():
            return pywFile.fileName()

        Colors.debug("- WARNING: Could not resolve executable:", dir.path(),
                fileName)
        return '__executable not found__'

    def resolveDocUrl(self, name):
        dirName = self.info[name]['dirname']
        category = self.info[name]['category']
        fileName = self.info[name]['filename']

        if category == 'demos':
            return self.helpRootUrl + 'demos-' + fileName + '.html'
        else:
            return self.helpRootUrl + dirName.replace('/', '-') + '-' + fileName + '.html'

    def resolveImageUrl(self, name):
        return self.helpRootUrl + 'images/' + name

    def getHtml(self, name):
        return self.getResource(self.resolveDocUrl(name))

    def getImage(self, name):
        imageName = self.info[name]['image']
        category = self.info[name]['category']
        fileName = self.info[name]['filename']

        if self.info[name]['qml'] == 'true':
            fileName = 'qml-' + fileName.split('/')[-1]

        if not imageName:
            if category == 'demos':
                imageName = fileName + '-demo.png'
            else:
                imageName = fileName + '-example.png'

            if self.getResource(self.resolveImageUrl(imageName)).isEmpty():
                imageName = fileName + '.png'

            if self.getResource(self.resolveImageUrl(imageName)).isEmpty():
                imageName = fileName + 'example.png'

        return self.getResource(self.resolveImageUrl(imageName))

    def createRootMenu(self, el):
        name = str(el.attribute('name'))
        self.createMenu(el, MenuManager.MENU1)
        self.createInfo(
                MenuContentItem(el, self.window.scene,
                        self.window.mainSceneRoot),
                name + ' -info')

        menuButtonsIn = self.score.insertMovie(name + ' -buttons')
        menuButtonsOut = self.score.insertMovie(name + ' -buttons -out')
        self.createLowLeftButton("Quit", MenuManager.QUIT, menuButtonsIn,
                menuButtonsOut, None)
        self.createLowRightButton("Toggle fullscreen", MenuManager.FULLSCREEN,
                menuButtonsIn, menuButtonsOut, None)

    def createSubMenu(self, el):
        name = str(el.attribute('name'))
        self.createMenu(el, MenuManager.MENU2)
        self.createInfo(
                MenuContentItem(el, self.window.scene,
                        self.window.mainSceneRoot),
                name + ' -info')

    def createLeafMenu(self, el):
        name = str(el.attribute('name'))
        self.createInfo(
                ExampleContent(name, self.window.scene,
                        self.window.mainSceneRoot),
                name)

        infoButtonsIn = self.score.insertMovie(name + ' -buttons')
        infoButtonsOut = self.score.insertMovie(name + ' -buttons -out')
        self.createLowRightLeafButton("Documentation", 600,
                MenuManager.DOCUMENTATION, infoButtonsIn, infoButtonsOut, None)
        if str(el.attribute('executable')) != 'false':
            self.createLowRightLeafButton("Launch", 405, MenuManager.LAUNCH,
                    infoButtonsIn, infoButtonsOut, None)
        elif str(el.attribute('qml')) == 'true':
            self.createLowRightLeafButton("Display", 405,
                    MenuManager.LAUNCH_QML, infoButtonsIn, infoButtonsOut,
                    None)

    def createMenu(self, category, type):
        sw = self.window.scene.sceneRect().width()
        xOffset = 15
        yOffset = 10
        maxExamples = Colors.menuCount
        menuIndex = 1
        name = str(category.attribute('name'))
        currentNode = category.firstChild()
        currentMenu = '%s -menu%d' % (name, menuIndex)

        while not currentNode.isNull():
            movieIn = self.score.insertMovie(currentMenu)
            movieOut = self.score.insertMovie(currentMenu + ' -out')
            movieNextTopOut = self.score.insertMovie(currentMenu + ' -top_out')
            movieNextBottomOut = self.score.insertMovie(currentMenu + ' -bottom_out')
            movieNextTopIn = self.score.insertMovie(currentMenu + ' -top_in')
            movieNextBottomIn = self.score.insertMovie(currentMenu + ' -bottom_in')
            movieShake = self.score.insertMovie(currentMenu + ' -shake')

            i = 0
            while not currentNode.isNull() and i < maxExamples:
                # Create a normal menu button.
                label = str(currentNode.toElement().attribute('name'))
                item = TextButton(label, TextButton.LEFT, type,
                        self.window.scene, self.window.mainSceneRoot)
                currentNode = currentNode.nextSibling()

                # Skip the OpenGL examples if they can't run.
                if str(currentNode.toElement().attribute('dirname')) == 'opengl':
                    if not Colors.openGlAvailable:
                        currentNode = currentNode.nextSibling()

                item.setRecursiveVisible(False)
                item.setZValue(10)
                ih = item.sceneBoundingRect().height()
                iw = item.sceneBoundingRect().width()
                ihp = ih + 3

                # Create in-animation.
                anim = DemoItemAnimation(item, DemoItemAnimation.ANIM_IN)
                anim.setDuration(float(1000 + (i * 20)) * Colors.animSpeedButtons)
                anim.setStartPos(QtCore.QPointF(xOffset, -ih))
                anim.setPosAt(0.20, QtCore.QPointF(xOffset, -ih))
                anim.setPosAt(0.50, QtCore.QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY + (10 * float(i / 4.0))))
                anim.setPosAt(0.60, QtCore.QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY))
                anim.setPosAt(0.70, QtCore.QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY + (5 * float(i / 4.0))))
                anim.setPosAt(0.80, QtCore.QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY))
                anim.setPosAt(0.90, QtCore.QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY + (2 * float(i / 4.0))))
                anim.setPosAt(1.00, QtCore.QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY))
                movieIn.append(anim)

                # Create out-animation.
                anim = DemoItemAnimation(item, DemoItemAnimation.ANIM_OUT)
                anim.hideOnFinished = True
                anim.setDuration((700 + (30 * i)) * Colors.animSpeedButtons)
                anim.setStartPos(QtCore.QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY))
                anim.setPosAt(0.60, QtCore.QPointF(xOffset, 600 - ih - ih))
                anim.setPosAt(0.65, QtCore.QPointF(xOffset + 20, 600 - ih))
                anim.setPosAt(1.00, QtCore.QPointF(sw + iw, 600 - ih))
                movieOut.append(anim)

                # Create shake-animation.
                anim = DemoItemAnimation(item)
                anim.setDuration(700 * Colors.animSpeedButtons)
                anim.setStartPos(QtCore.QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY))
                anim.setPosAt(0.55, QtCore.QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY - i*2.0))
                anim.setPosAt(0.70, QtCore.QPointF(xOffset - 10, (i * ihp) + yOffset + Colors.contentStartY - i*1.5))
                anim.setPosAt(0.80, QtCore.QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY - i*1.0))
                anim.setPosAt(0.90, QtCore.QPointF(xOffset - 2, (i * ihp) + yOffset + Colors.contentStartY - i*0.5))
                anim.setPosAt(1.00, QtCore.QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY))
                movieShake.append(anim)

                # Create next-menu top-out-animation.
                anim = DemoItemAnimation(item, DemoItemAnimation.ANIM_OUT)
                anim.hideOnFinished = True
                anim.setDuration((200 + (30 * i)) * Colors.animSpeedButtons)
                anim.setStartPos(QtCore.QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY))
                anim.setPosAt(0.70, QtCore.QPointF(xOffset, yOffset + Colors.contentStartY))
                anim.setPosAt(1.00, QtCore.QPointF(-iw, yOffset + Colors.contentStartY))
                movieNextTopOut.append(anim)

                # Create next-menu bottom-out-animation.
                anim = DemoItemAnimation(item, DemoItemAnimation.ANIM_OUT)
                anim.hideOnFinished = True
                anim.setDuration((200 + (30 * i)) * Colors.animSpeedButtons)
                anim.setStartPos(QtCore.QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY))
                anim.setPosAt(0.70, QtCore.QPointF(xOffset, (maxExamples * ihp) + yOffset + Colors.contentStartY))
                anim.setPosAt(1.00, QtCore.QPointF(-iw, (maxExamples * ihp) + yOffset + Colors.contentStartY))
                movieNextBottomOut.append(anim)

                # Create next-menu top-in-animation.
                anim = DemoItemAnimation(item, DemoItemAnimation.ANIM_IN)
                anim.setDuration((700 - (30 * i)) * Colors.animSpeedButtons)
                anim.setStartPos(QtCore.QPointF(-iw, yOffset + Colors.contentStartY))
                anim.setPosAt(0.30, QtCore.QPointF(xOffset, yOffset + Colors.contentStartY))
                anim.setPosAt(1.00, QtCore.QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY))
                movieNextTopIn.append(anim)

                # Create next-menu bottom-in-animation.
                reverse = maxExamples - i
                anim = DemoItemAnimation(item, DemoItemAnimation.ANIM_IN)
                anim.setDuration((1000 - (30 * reverse)) * Colors.animSpeedButtons)
                anim.setStartPos(QtCore.QPointF(-iw, (maxExamples * ihp) + yOffset + Colors.contentStartY))
                anim.setPosAt(0.30, QtCore.QPointF(xOffset, (maxExamples * ihp) + yOffset + Colors.contentStartY))
                anim.setPosAt(1.00, QtCore.QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY))
                movieNextBottomIn.append(anim)

                i += 1

            if not currentNode.isNull() and i == maxExamples:
                # We need another menu, so register for 'more' and 'back'
                # buttons.
                menuIndex += 1
                self.info.setdefault(currentMenu, {})['more'] = '%s -menu%d' % (name, menuIndex)
                currentMenu = '%s -menu%d' % (name, menuIndex)
                self.info.setdefault(currentMenu, {})['back'] = '%s -menu%d' % (name, menuIndex - 1)

    def createLowLeftButton(self, label, type, movieIn, movieOut, movieShake, menuString=""):
        button = TextButton(label, TextButton.RIGHT, type, self.window.scene,
                self.window.mainSceneRoot, TextButton.PANEL)
        if menuString:
            button.setMenuString(menuString)
        button.setRecursiveVisible(False)
        button.setZValue(10)

        iw = button.sceneBoundingRect().width()
        xOffset = 15

        # Create in-animation.
        buttonIn = DemoItemAnimation(button, DemoItemAnimation.ANIM_IN)
        buttonIn.setDuration(1800 * Colors.animSpeedButtons)
        buttonIn.setStartPos(QtCore.QPointF(-iw, Colors.contentStartY + Colors.contentHeight - 35))
        buttonIn.setPosAt(0.5, QtCore.QPointF(-iw, Colors.contentStartY + Colors.contentHeight - 35))
        buttonIn.setPosAt(0.7, QtCore.QPointF(xOffset, Colors.contentStartY + Colors.contentHeight - 35))
        buttonIn.setPosAt(1.0, QtCore.QPointF(xOffset, Colors.contentStartY + Colors.contentHeight - 26))
        movieIn.append(buttonIn)

        # Create out-animation.
        buttonOut = DemoItemAnimation(button, DemoItemAnimation.ANIM_OUT)
        buttonOut.hideOnFinished = True
        buttonOut.setDuration(400 * Colors.animSpeedButtons)
        buttonOut.setStartPos(QtCore.QPointF(xOffset, Colors.contentStartY + Colors.contentHeight - 26))
        buttonOut.setPosAt(1.0, QtCore.QPointF(-iw, Colors.contentStartY + Colors.contentHeight - 26))
        movieOut.append(buttonOut)

        if movieShake is not None:
            shakeAnim = DemoItemAnimation(button, DemoItemAnimation.ANIM_UNSPECIFIED)
            shakeAnim.timeline.setCurveShape(QtCore.QTimeLine.LinearCurve)
            shakeAnim.setDuration(650)
            shakeAnim.setStartPos(buttonIn.posAt(1.0))
            shakeAnim.setPosAt(0.60, buttonIn.posAt(1.0))
            shakeAnim.setPosAt(0.70, buttonIn.posAt(1.0) + QtCore.QPointF(-3, 0))
            shakeAnim.setPosAt(0.80, buttonIn.posAt(1.0) + QtCore.QPointF(2, 0))
            shakeAnim.setPosAt(0.90, buttonIn.posAt(1.0) + QtCore.QPointF(-1, 0))
            shakeAnim.setPosAt(1.00, buttonIn.posAt(1.0))
            movieShake.append(shakeAnim)

    def createLowRightButton(self, label, type, movieIn, movieOut, movieShake):
        item = TextButton(label, TextButton.RIGHT, type, self.window.scene,
                self.window.mainSceneRoot, TextButton.PANEL)
        item.setRecursiveVisible(False)
        item.setZValue(10)

        sw = self.window.scene.sceneRect().width()
        xOffset = 70

        # Create in-animation.
        anim = DemoItemAnimation(item, DemoItemAnimation.ANIM_IN)
        anim.setDuration(1800 * Colors.animSpeedButtons)
        anim.setStartPos(QtCore.QPointF(sw, Colors.contentStartY + Colors.contentHeight - 35))
        anim.setPosAt(0.5, QtCore.QPointF(sw, Colors.contentStartY + Colors.contentHeight - 35))
        anim.setPosAt(0.7, QtCore.QPointF(xOffset + 535, Colors.contentStartY + Colors.contentHeight - 35))
        anim.setPosAt(1.0, QtCore.QPointF(xOffset + 535, Colors.contentStartY + Colors.contentHeight - 26))
        movieIn.append(anim)

        # Create out-animation.
        anim = DemoItemAnimation(item, DemoItemAnimation.ANIM_OUT)
        anim.hideOnFinished = True
        anim.setDuration(400 * Colors.animSpeedButtons)
        anim.setStartPos(QtCore.QPointF(xOffset + 535, Colors.contentStartY + Colors.contentHeight - 26))
        anim.setPosAt(1.0, QtCore.QPointF(sw, Colors.contentStartY + Colors.contentHeight - 26))
        movieOut.append(anim)

    def createLowRightLeafButton(self, label, xOffset, type, movieIn, movieOut, movieShake):
        item = TextButton(label, TextButton.RIGHT, type, self.window.scene,
                self.window.mainSceneRoot, TextButton.PANEL)
        item.setRecursiveVisible(False)
        item.setZValue(10)

        sw = self.window.scene.sceneRect().width()
        sh = self.window.scene.sceneRect().height()

        # Create in-animation.
        anim = DemoItemAnimation(item, DemoItemAnimation.ANIM_IN)
        anim.setDuration(1050 * Colors.animSpeedButtons)
        anim.setStartPos(QtCore.QPointF(sw, Colors.contentStartY + Colors.contentHeight - 35))
        anim.setPosAt(0.10, QtCore.QPointF(sw, Colors.contentStartY + Colors.contentHeight - 35))
        anim.setPosAt(0.30, QtCore.QPointF(xOffset, Colors.contentStartY + Colors.contentHeight - 35))
        anim.setPosAt(0.35, QtCore.QPointF(xOffset + 30, Colors.contentStartY + Colors.contentHeight - 35))
        anim.setPosAt(0.40, QtCore.QPointF(xOffset, Colors.contentStartY + Colors.contentHeight - 35))
        anim.setPosAt(0.45, QtCore.QPointF(xOffset + 5, Colors.contentStartY + Colors.contentHeight - 35))
        anim.setPosAt(0.50, QtCore.QPointF(xOffset, Colors.contentStartY + Colors.contentHeight - 35))
        anim.setPosAt(1.00, QtCore.QPointF(xOffset, Colors.contentStartY + Colors.contentHeight - 26))
        movieIn.append(anim)

        # Create out-animation.
        anim = DemoItemAnimation(item, DemoItemAnimation.ANIM_OUT)
        anim.hideOnFinished = True
        anim.setDuration(300 * Colors.animSpeedButtons)
        anim.setStartPos(QtCore.QPointF(xOffset, Colors.contentStartY + Colors.contentHeight - 26))
        anim.setPosAt(1.0, QtCore.QPointF(xOffset, sh))
        movieOut.append(anim)

    def createInfo(self, item, name):
        movie_in = self.score.insertMovie(name)
        movie_out = self.score.insertMovie(name + ' -out')
        item.setZValue(8)
        item.setRecursiveVisible(False)

        xOffset = 230.0
        infoIn = DemoItemAnimation(item, DemoItemAnimation.ANIM_IN)
        infoIn.timeline.setCurveShape(QtCore.QTimeLine.LinearCurve)
        infoIn.setDuration(650)
        infoIn.setStartPos(QtCore.QPointF(self.window.scene.sceneRect().width(), Colors.contentStartY))
        infoIn.setPosAt(0.60, QtCore.QPointF(xOffset, Colors.contentStartY))
        infoIn.setPosAt(0.70, QtCore.QPointF(xOffset + 20, Colors.contentStartY))
        infoIn.setPosAt(0.80, QtCore.QPointF(xOffset, Colors.contentStartY))
        infoIn.setPosAt(0.90, QtCore.QPointF(xOffset + 7, Colors.contentStartY))
        infoIn.setPosAt(1.00, QtCore.QPointF(xOffset, Colors.contentStartY))
        movie_in.append(infoIn)

        infoOut = DemoItemAnimation(item, DemoItemAnimation.ANIM_OUT)
        infoOut.timeline.setCurveShape(QtCore.QTimeLine.EaseInCurve)
        infoOut.setDuration(300)
        infoOut.hideOnFinished = True
        infoOut.setStartPos(QtCore.QPointF(xOffset, Colors.contentStartY))
        infoOut.setPosAt(1.0, QtCore.QPointF(-600, Colors.contentStartY))
        movie_out.append(infoOut)

    def createTicker(self):
        if Colors.noTicker:
            return

        movie_in = self.score.insertMovie('ticker')
        movie_out = self.score.insertMovie('ticker -out')
        movie_activate = self.score.insertMovie('ticker -activate')
        movie_deactivate = self.score.insertMovie('ticker -deactivate')

        self.ticker = ItemCircleAnimation(self.window.scene)
        self.ticker.setZValue(50)
        self.ticker.hide()

        # Move ticker in.
        qtendpos = 485
        qtPosY = 120
        self.tickerInAnim = DemoItemAnimation(self.ticker,
                DemoItemAnimation.ANIM_IN)
        self.tickerInAnim.setDuration(500)
        self.tickerInAnim.setStartPos(QtCore.QPointF(self.window.scene.sceneRect().width(), Colors.contentStartY + qtPosY))
        self.tickerInAnim.setPosAt(0.60, QtCore.QPointF(qtendpos, Colors.contentStartY + qtPosY))
        self.tickerInAnim.setPosAt(0.70, QtCore.QPointF(qtendpos + 30, Colors.contentStartY + qtPosY))
        self.tickerInAnim.setPosAt(0.80, QtCore.QPointF(qtendpos, Colors.contentStartY + qtPosY))
        self.tickerInAnim.setPosAt(0.90, QtCore.QPointF(qtendpos + 5, Colors.contentStartY + qtPosY))
        self.tickerInAnim.setPosAt(1.00, QtCore.QPointF(qtendpos, Colors.contentStartY + qtPosY))
        movie_in.append(self.tickerInAnim)

        # Move ticker out.
        qtOut = DemoItemAnimation(self.ticker, DemoItemAnimation.ANIM_OUT)
        qtOut.hideOnFinished = True
        qtOut.setDuration(500)
        qtOut.setStartPos(QtCore.QPointF(qtendpos, Colors.contentStartY + qtPosY))
        qtOut.setPosAt(1.00, QtCore.QPointF(self.window.scene.sceneRect().width() + 700, Colors.contentStartY + qtPosY))
        movie_out.append(qtOut)

        # Move ticker in on activate.
        qtActivate = DemoItemAnimation(self.ticker)
        qtActivate.setDuration(400)
        qtActivate.setStartPos(QtCore.QPointF(self.window.scene.sceneRect().width(), Colors.contentStartY + qtPosY))
        qtActivate.setPosAt(0.60, QtCore.QPointF(qtendpos, Colors.contentStartY + qtPosY))
        qtActivate.setPosAt(0.70, QtCore.QPointF(qtendpos + 30, Colors.contentStartY + qtPosY))
        qtActivate.setPosAt(0.80, QtCore.QPointF(qtendpos, Colors.contentStartY + qtPosY))
        qtActivate.setPosAt(0.90, QtCore.QPointF(qtendpos + 5, Colors.contentStartY + qtPosY))
        qtActivate.setPosAt(1.00, QtCore.QPointF(qtendpos, Colors.contentStartY + qtPosY))
        movie_activate.append(qtActivate)

        # Move ticker out on deactivate.
        qtDeactivate = DemoItemAnimation(self.ticker)
        qtDeactivate.hideOnFinished = True
        qtDeactivate.setDuration(400)
        qtDeactivate.setStartPos(QtCore.QPointF(qtendpos, Colors.contentStartY + qtPosY))
        qtDeactivate.setPosAt(1.00, QtCore.QPointF(qtendpos, 800))
        movie_deactivate.append(qtDeactivate)

    def createUpnDownButtons(self):
        xOffset = 15.0
        yOffset = 450.0

        self.upButton = TextButton("", TextButton.LEFT, MenuManager.UP,
                self.window.scene, self.window.mainSceneRoot, TextButton.UP)
        self.upButton.prepare()
        self.upButton.setPos(xOffset, yOffset)
        self.upButton.setState(TextButton.DISABLED)

        self.downButton = TextButton("", TextButton.LEFT, MenuManager.DOWN,
                self.window.scene, self.window.mainSceneRoot, TextButton.DOWN)
        self.downButton.prepare()
        self.downButton.setPos(xOffset + 10 + self.downButton.sceneBoundingRect().width(), yOffset)

        movieShake = self.score.insertMovie('upndown -shake')

        shakeAnim = DemoItemAnimation(self.upButton,
                DemoItemAnimation.ANIM_UNSPECIFIED)
        shakeAnim.timeline.setCurveShape(QtCore.QTimeLine.LinearCurve)
        shakeAnim.setDuration(650)
        shakeAnim.setStartPos(self.upButton.pos())
        shakeAnim.setPosAt(0.60, self.upButton.pos())
        shakeAnim.setPosAt(0.70, self.upButton.pos() + QtCore.QPointF(-2, 0))
        shakeAnim.setPosAt(0.80, self.upButton.pos() + QtCore.QPointF(1, 0))
        shakeAnim.setPosAt(0.90, self.upButton.pos() + QtCore.QPointF(-1, 0))
        shakeAnim.setPosAt(1.00, self.upButton.pos())
        movieShake.append(shakeAnim)

        shakeAnim = DemoItemAnimation(self.downButton,
                DemoItemAnimation.ANIM_UNSPECIFIED)
        shakeAnim.timeline.setCurveShape(QtCore.QTimeLine.LinearCurve)
        shakeAnim.setDuration(650)
        shakeAnim.setStartPos(self.downButton.pos())
        shakeAnim.setPosAt(0.60, self.downButton.pos())
        shakeAnim.setPosAt(0.70, self.downButton.pos() + QtCore.QPointF(-5, 0))
        shakeAnim.setPosAt(0.80, self.downButton.pos() + QtCore.QPointF(-3, 0))
        shakeAnim.setPosAt(0.90, self.downButton.pos() + QtCore.QPointF(-1, 0))
        shakeAnim.setPosAt(1.00, self.downButton.pos())
        movieShake.append(shakeAnim)

    def createBackButton(self):
        backIn = self.score.insertMovie('back -in')
        backOut = self.score.insertMovie('back -out')
        backShake = self.score.insertMovie('back -shake')
        self.createLowLeftButton("Back", MenuManager.ROOT, backIn, backOut,
                backShake, Colors.rootMenuName)
