1#############################################################################
2##
3## Copyright (C) 2013 Riverbank Computing Limited.
4## Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
5## All rights reserved.
6##
7## This file is part of the examples of PyQt.
8##
9## $QT_BEGIN_LICENSE:LGPL$
10## Commercial Usage
11## Licensees holding valid Qt Commercial licenses may use this file in
12## accordance with the Qt Commercial License Agreement provided with the
13## Software or, alternatively, in accordance with the terms contained in
14## a written agreement between you and Nokia.
15##
16## GNU Lesser General Public License Usage
17## Alternatively, this file may be used under the terms of the GNU Lesser
18## General Public License version 2.1 as published by the Free Software
19## Foundation and appearing in the file LICENSE.LGPL included in the
20## packaging of this file.  Please review the following information to
21## ensure the GNU Lesser General Public License version 2.1 requirements
22## will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23##
24## In addition, as a special exception, Nokia gives you certain additional
25## rights.  These rights are described in the Nokia Qt LGPL Exception
26## version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27##
28## GNU General Public License Usage
29## Alternatively, this file may be used under the terms of the GNU
30## General Public License version 3.0 as published by the Free Software
31## Foundation and appearing in the file LICENSE.GPL included in the
32## packaging of this file.  Please review the following information to
33## ensure the GNU General Public License version 3.0 requirements will be
34## met: http://www.gnu.org/copyleft/gpl.html.
35##
36## If you have questions regarding the use of this file, please contact
37## Nokia at qt-info@nokia.com.
38## $QT_END_LICENSE$
39##
40#############################################################################
41
42
43import sys
44from xml.dom.minidom import parseString
45
46from PyQt5.QtCore import (QByteArray, QDir, QEasingCurve, QFile, QFileInfo,
47        QLibraryInfo, QObject, QPointF, QProcess, QProcessEnvironment,
48        QStandardPaths, Qt, QT_VERSION, QT_VERSION_STR, QTextStream, QUrl)
49from PyQt5.QtWidgets import QApplication, QMessageBox
50
51from colors import Colors
52from demoitemanimation import DemoItemAnimation
53from examplecontent import ExampleContent
54from itemcircleanimation import ItemCircleAnimation
55from menucontent import MenuContentItem
56from score import Score
57from textbutton import TextButton
58
59
60class MenuManager(QObject):
61    ROOT, MENU1, MENU2, LAUNCH, DOCUMENTATION, QUIT, FULLSCREEN, UP, DOWN, \
62            BACK, LAUNCH_QML = range(11)
63
64    pInstance = None
65
66    def __init__(self):
67        super(MenuManager, self).__init__()
68
69        self.contentsDoc = None
70        self.assistantProcess = QProcess()
71        self.helpRootUrl = ''
72        self.docDir = QDir()
73        self.imgDir = QDir()
74
75        self.info = {}
76        self.window = None
77
78        self.ticker = None
79        self.tickerInAnim = None
80        self.upButton = None
81        self.downButton = None
82        self.score = Score()
83        self.currentMenu = "[no menu visible]"
84        self.currentCategory = "[no category visible]"
85        self.currentMenuButtons = "[no menu buttons visible]"
86        self.currentInfo = "[no info visible]"
87        self.currentMenuCode = -1
88        self.readXmlDocument()
89
90    @classmethod
91    def instance(cls):
92        if cls.pInstance is None:
93            cls.pInstance = cls()
94
95        return cls.pInstance
96
97    def getResource(self, name):
98        return QByteArray()
99
100    def readXmlDocument(self):
101        root = QFileInfo(__file__).absolutePath()
102        xml_file = QFile(root + '/examples.xml')
103        xml_file.open(QFile.ReadOnly | QFile.Text)
104        contents = xml_file.readAll().data()
105        xml_file.close()
106
107        self.contentsDoc = parseString(contents)
108
109    def itemSelected(self, userCode, menuName):
110        if userCode == MenuManager.LAUNCH:
111            self.launchExample(self.currentInfo)
112        elif userCode == MenuManager.LAUNCH_QML:
113            self.launchQml(self.currentInfo)
114        elif userCode == MenuManager.DOCUMENTATION:
115            self.showDocInAssistant(self.currentInfo)
116        elif userCode == MenuManager.QUIT:
117            QApplication.quit()
118        elif userCode == MenuManager.FULLSCREEN:
119            self.window.toggleFullscreen()
120        elif userCode == MenuManager.ROOT:
121            # Out.
122            self.score.queueMovie(self.currentMenu + ' -out', Score.FROM_START,
123                    Score.LOCK_ITEMS)
124            self.score.queueMovie(self.currentMenuButtons + ' -out',
125                    Score.FROM_START, Score.LOCK_ITEMS)
126            self.score.queueMovie(self.currentInfo + ' -out')
127            self.score.queueMovie(self.currentInfo + ' -buttons -out',
128                    Score.NEW_ANIMATION_ONLY)
129            self.score.queueMovie('back -out', Score.ONLY_IF_VISIBLE)
130
131            # Book-keeping.
132            self.currentMenuCode = MenuManager.ROOT
133            self.currentMenu = menuName + ' -menu1'
134            self.currentMenuButtons = menuName + ' -buttons'
135            self.currentInfo = menuName + ' -info'
136
137            # In.
138            self.score.queueMovie('upndown -shake')
139            self.score.queueMovie(self.currentMenu, Score.FROM_START,
140                    Score.UNLOCK_ITEMS)
141            self.score.queueMovie(self.currentMenuButtons, Score.FROM_START,
142                    Score.UNLOCK_ITEMS)
143            self.score.queueMovie(self.currentInfo)
144
145            if not Colors.noTicker:
146                self.ticker.doIntroTransitions = True
147                self.tickerInAnim.setStartDelay(2000)
148                self.ticker.useGuideQt()
149                self.score.queueMovie('ticker', Score.NEW_ANIMATION_ONLY)
150        elif userCode == MenuManager.MENU1:
151            # Out.
152            self.score.queueMovie(self.currentMenu + ' -out', Score.FROM_START,
153                    Score.LOCK_ITEMS)
154            self.score.queueMovie(self.currentMenuButtons + ' -out',
155                    Score.FROM_START, Score.LOCK_ITEMS)
156            self.score.queueMovie(self.currentInfo + ' -out')
157
158            # Book-keeping.
159            self.currentMenuCode = MenuManager.MENU1
160            self.currentCategory = menuName
161            self.currentMenu = menuName + ' -menu1'
162            self.currentInfo = menuName + ' -info'
163
164            # In.
165            self.score.queueMovie('upndown -shake')
166            self.score.queueMovie('back -in')
167            self.score.queueMovie(self.currentMenu, Score.FROM_START,
168                    Score.UNLOCK_ITEMS)
169            self.score.queueMovie(self.currentInfo)
170
171            if not Colors.noTicker:
172                self.ticker.useGuideTt()
173        elif userCode == MenuManager.MENU2:
174            # Out.
175            self.score.queueMovie(self.currentInfo + ' -out',
176                    Score.NEW_ANIMATION_ONLY)
177            self.score.queueMovie(self.currentInfo + ' -buttons -out',
178                    Score.NEW_ANIMATION_ONLY)
179
180            # Book-keeping.
181            self.currentMenuCode = MenuManager.MENU2
182            self.currentInfo = menuName
183
184            # In/shake.
185            self.score.queueMovie('upndown -shake')
186            self.score.queueMovie('back -shake')
187            self.score.queueMovie(self.currentMenu + ' -shake')
188            self.score.queueMovie(self.currentInfo, Score.NEW_ANIMATION_ONLY)
189            self.score.queueMovie(self.currentInfo + ' -buttons',
190                    Score.NEW_ANIMATION_ONLY)
191
192            if not Colors.noTicker:
193                self.score.queueMovie('ticker -out', Score.NEW_ANIMATION_ONLY)
194        elif userCode == MenuManager.UP:
195            backMenu = self.info[self.currentMenu]['back']
196            if backMenu:
197                self.score.queueMovie(self.currentMenu + ' -top_out',
198                        Score.FROM_START, Score.LOCK_ITEMS)
199                self.score.queueMovie(backMenu + ' -bottom_in',
200                        Score.FROM_START, Score.UNLOCK_ITEMS)
201                self.currentMenu = backMenu
202        elif userCode == MenuManager.DOWN:
203            moreMenu = self.info[self.currentMenu]['more']
204            if moreMenu:
205                self.score.queueMovie(self.currentMenu + ' -bottom_out',
206                        Score.FROM_START, Score.LOCK_ITEMS)
207                self.score.queueMovie(moreMenu + ' -top_in', Score.FROM_START,
208                        Score.UNLOCK_ITEMS)
209                self.currentMenu = moreMenu
210        elif userCode == MenuManager.BACK:
211            if self.currentMenuCode == MenuManager.MENU2:
212                # Out.
213                self.score.queueMovie(self.currentInfo + ' -out',
214                        Score.NEW_ANIMATION_ONLY)
215                self.score.queueMovie(self.currentInfo + ' -buttons -out',
216                        Score.NEW_ANIMATION_ONLY)
217
218                # Book-keeping.
219                self.currentMenuCode = MenuManager.MENU1
220                self.currentMenuButtons = self.currentCategory + ' -buttons'
221                self.currentInfo = self.currentCategory + ' -info'
222
223                # In/shake.
224                self.score.queueMovie('upndown -shake')
225                self.score.queueMovie(self.currentMenu + ' -shake')
226                self.score.queueMovie(self.currentInfo,
227                        Score.NEW_ANIMATION_ONLY)
228                self.score.queueMovie(self.currentInfo + ' -buttons',
229                        Score.NEW_ANIMATION_ONLY)
230
231                if not Colors.noTicker:
232                    self.ticker.doIntroTransitions = False
233                    self.tickerInAnim.setStartDelay(500)
234                    self.score.queueMovie('ticker', Score.NEW_ANIMATION_ONLY)
235            elif self.currentMenuCode != MenuManager.ROOT:
236                self.itemSelected(MenuManager.ROOT, Colors.rootMenuName)
237
238        # Update back and more buttons.
239        if self.info.setdefault(self.currentMenu, {}).get('back'):
240            back_state = TextButton.OFF
241        else:
242            back_state = TextButton.DISABLED
243
244        if self.info[self.currentMenu].get('more'):
245            more_state = TextButton.OFF
246        else:
247            more_state = TextButton.DISABLED
248
249        self.upButton.setState(back_state)
250        self.downButton.setState(more_state)
251
252        if self.score.hasQueuedMovies():
253            self.score.playQue()
254            # Playing new movies might include loading etc., so ignore the FPS
255            # at this point.
256            self.window.fpsHistory = []
257
258    def showDocInAssistant(self, name):
259        url = self.resolveDocUrl(name)
260        Colors.debug("Sending URL to Assistant:", url)
261
262        # Start assistant if it's not already running.
263        if self.assistantProcess.state() != QProcess.Running:
264            app = QLibraryInfo.location(QLibraryInfo.BinariesPath) + QDir.separator()
265
266            if sys.platform == 'darwin':
267                app += 'Assistant.app/Contents/MacOS/Assistant'
268            else:
269                app += 'assistant'
270
271            args = ['-enableRemoteControl']
272            self.assistantProcess.start(app, args)
273            if not self.assistantProcess.waitForStarted():
274                QMessageBox.critical(None, "PyQt Demo",
275                        "Could not start %s." % app)
276                return
277
278        # Send command through remote control even if the process was just
279        # started to activate assistant and bring it to the front.
280        cmd_str = QTextStream(self.assistantProcess)
281        cmd_str << 'SetSource ' << url << '\n'
282
283    def launchExample(self, name):
284        executable = self.resolveExeFile(name)
285
286        process = QProcess(self)
287        process.error.connect(self.launchError)
288
289        if sys.platform == 'win32':
290            # Make sure it finds the DLLs on Windows.
291            env = QProcessEnvironment.systemEnvironment()
292            env.insert('PATH',
293                    QLibraryInfo.location(QLibraryInfo.BinariesPath) + ';' +
294                            env.value('PATH'))
295            process.setProcessEnvironment(env)
296
297        if self.info[name]['changedirectory'] != 'false':
298            workingDirectory = self.resolveDataDir(name)
299            process.setWorkingDirectory(workingDirectory)
300            Colors.debug("Setting working directory:", workingDirectory)
301
302        Colors.debug("Launching:", executable)
303        process.start(sys.executable, [executable])
304
305    def launchQml(self, name):
306        import_path = self.resolveDataDir(name)
307        qml = self.resolveQmlFile(name)
308
309        process = QProcess(self)
310        process.error.connect(self.launchError)
311
312        env = QProcessEnvironment.systemEnvironment()
313        env.insert('QML2_IMPORT_PATH', import_path)
314        process.setProcessEnvironment(env)
315
316        executable = QLibraryInfo.location(QLibraryInfo.BinariesPath) + '/qmlscene'
317        Colors.debug("Launching:", executable)
318        process.start(executable, [qml])
319
320    def launchError(self, error):
321        if error != QProcess.Crashed:
322            QMessageBox.critical(None, "Failed to launch the example",
323                    "Could not launch the example. Ensure that it has been "
324                    "built.",
325                    QMessageBox.Cancel)
326
327    def init(self, window):
328        self.window = window
329
330        # Create div.
331        self.createTicker()
332        self.createUpnDownButtons()
333        self.createBackButton()
334
335        # Create first level menu.
336        rootElement = self.contentsDoc.documentElement
337        self.createRootMenu(rootElement)
338
339        # Create second level menus.
340        level2Menu = self._first_element(rootElement)
341        while level2Menu is not None:
342            self.createSubMenu(level2Menu)
343
344            # Create leaf menu and example info.
345            example = self._first_element(level2Menu)
346            while example is not None:
347                self.readInfoAboutExample(example)
348                self.createLeafMenu(example)
349                example = self._next_element(example)
350
351            level2Menu = self._next_element(level2Menu)
352
353    @classmethod
354    def _first_element(cls, node):
355        return cls._skip_nonelements(node.firstChild)
356
357    @classmethod
358    def _next_element(cls, node):
359        return cls._skip_nonelements(node.nextSibling)
360
361    @staticmethod
362    def _skip_nonelements(node):
363        while node is not None and node.nodeType != node.ELEMENT_NODE:
364            node = node.nextSibling
365
366        return node
367
368    def readInfoAboutExample(self, example):
369        name = example.getAttribute('name')
370        if name in self.info:
371            Colors.debug("__WARNING: MenuManager.readInfoAboutExample: "
372                         "Demo/example with name", name, "appears twice in "
373                         "the xml-file!__")
374
375        self.info.setdefault(name, {})['filename'] = example.getAttribute('filename')
376        self.info[name]['dirname'] = example.parentNode.getAttribute('dirname')
377        self.info[name]['changedirectory'] = example.getAttribute('changedirectory')
378        self.info[name]['image'] = example.getAttribute('image')
379        self.info[name]['qml'] = example.getAttribute('qml')
380
381    def resolveDir(self, name):
382        dirName = self.info[name]['dirname']
383        fileName = self.info[name]['filename'].split('/')
384
385        dir = QFileInfo(__file__).dir()
386        # To the 'examples' directory.
387        dir.cdUp()
388
389        dir.cd(dirName)
390
391        if len(fileName) > 1:
392            dir.cd('/'.join(fileName[:-1]))
393
394        # This may legitimately fail if the example is just a simple .py file.
395        dir.cd(fileName[-1])
396
397        return dir
398
399    def resolveDataDir(self, name):
400        return self.resolveDir(name).absolutePath()
401
402    def resolveExeFile(self, name):
403        dir = self.resolveDir(name)
404
405        fileName = self.info[name]['filename'].split('/')[-1]
406
407        pyFile = QFile(dir.path() + '/' + fileName + '.py')
408        if pyFile.exists():
409            return pyFile.fileName()
410
411        pywFile = QFile(dir.path() + '/' + fileName + '.pyw')
412        if pywFile.exists():
413            return pywFile.fileName()
414
415        Colors.debug("- WARNING: Could not resolve executable:", dir.path(),
416                fileName)
417        return '__executable not found__'
418
419    def resolveQmlFile(self, name):
420        dir = self.resolveDir(name)
421
422        fileName = self.info[name]['filename'].split('/')[-1]
423
424        qmlFile = QFile(dir.path() + '/' + fileName + '.qml')
425        if qmlFile.exists():
426            return qmlFile.fileName()
427
428        Colors.debug("- WARNING: Could not resolve QML file:", dir.path(),
429                fileName)
430        return '__QML not found__'
431
432    def resolveDocUrl(self, name):
433        dirName = self.info[name]['dirname']
434        fileName = self.info[name]['filename']
435
436        return self.helpRootUrl + dirName.replace('/', '-') + '-' + fileName + '.html'
437
438    def resolveImageUrl(self, name):
439        return self.helpRootUrl + 'images/' + name
440
441    def getHtml(self, name):
442        return self.getResource(self.resolveDocUrl(name))
443
444    def getImage(self, name):
445        imageName = self.info[name]['image']
446        fileName = self.info[name]['filename']
447
448        if self.info[name]['qml'] == 'true':
449            fileName = 'qml-' + fileName.split('/')[-1]
450
451        if not imageName:
452            imageName = fileName + '-example.png'
453
454            if self.getResource(self.resolveImageUrl(imageName)).isEmpty():
455                imageName = fileName + '.png'
456
457            if self.getResource(self.resolveImageUrl(imageName)).isEmpty():
458                imageName = fileName + 'example.png'
459
460        return self.getResource(self.resolveImageUrl(imageName))
461
462    def createRootMenu(self, el):
463        name = el.getAttribute('name')
464        self.createMenu(el, MenuManager.MENU1)
465        self.createInfo(
466                MenuContentItem(el, self.window.mainSceneRoot),
467                name + ' -info')
468
469        menuButtonsIn = self.score.insertMovie(name + ' -buttons')
470        menuButtonsOut = self.score.insertMovie(name + ' -buttons -out')
471        self.createLowLeftButton("Quit", MenuManager.QUIT, menuButtonsIn,
472                menuButtonsOut, None)
473        self.createLowRightButton("Toggle fullscreen", MenuManager.FULLSCREEN,
474                menuButtonsIn, menuButtonsOut, None)
475
476    def createSubMenu(self, el):
477        name = el.getAttribute('name')
478        self.createMenu(el, MenuManager.MENU2)
479        self.createInfo(
480                MenuContentItem(el, self.window.mainSceneRoot),
481                name + ' -info')
482
483    def createLeafMenu(self, el):
484        name = el.getAttribute('name')
485        self.createInfo(ExampleContent(name, self.window.mainSceneRoot), name)
486
487        infoButtonsIn = self.score.insertMovie(name + ' -buttons')
488        infoButtonsOut = self.score.insertMovie(name + ' -buttons -out')
489        self.createLowRightLeafButton("Documentation", 600,
490                MenuManager.DOCUMENTATION, infoButtonsIn, infoButtonsOut, None)
491        if el.getAttribute('executable') != 'false':
492            self.createLowRightLeafButton("Launch", 405, MenuManager.LAUNCH,
493                    infoButtonsIn, infoButtonsOut, None)
494        elif el.getAttribute('qml') == 'true':
495            self.createLowRightLeafButton("Display", 405,
496                    MenuManager.LAUNCH_QML, infoButtonsIn, infoButtonsOut,
497                    None)
498
499    def createMenu(self, category, type):
500        sw = self.window.scene.sceneRect().width()
501        xOffset = 15
502        yOffset = 10
503        maxExamples = Colors.menuCount
504        menuIndex = 1
505        name = category.getAttribute('name')
506        currentNode = self._first_element(category)
507        currentMenu = '%s -menu%d' % (name, menuIndex)
508
509        while currentNode is not None:
510            movieIn = self.score.insertMovie(currentMenu)
511            movieOut = self.score.insertMovie(currentMenu + ' -out')
512            movieNextTopOut = self.score.insertMovie(currentMenu + ' -top_out')
513            movieNextBottomOut = self.score.insertMovie(currentMenu + ' -bottom_out')
514            movieNextTopIn = self.score.insertMovie(currentMenu + ' -top_in')
515            movieNextBottomIn = self.score.insertMovie(currentMenu + ' -bottom_in')
516            movieShake = self.score.insertMovie(currentMenu + ' -shake')
517
518            i = 0
519            while currentNode is not None and i < maxExamples:
520                # Create a normal menu button.
521                label = currentNode.getAttribute('name')
522                item = TextButton(label, TextButton.LEFT, type,
523                        self.window.mainSceneRoot)
524
525                item.setRecursiveVisible(False)
526                item.setZValue(10)
527                ih = item.sceneBoundingRect().height()
528                iw = item.sceneBoundingRect().width()
529                ihp = ih + 3
530
531                # Create in-animation.
532                anim = DemoItemAnimation(item, DemoItemAnimation.ANIM_IN)
533                anim.setDuration(1000 + (i * 20))
534                anim.setStartValue(QPointF(xOffset, -ih))
535                anim.setKeyValueAt(0.20, QPointF(xOffset, -ih))
536                anim.setKeyValueAt(0.50, QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY + (10 * float(i / 4.0))))
537                anim.setKeyValueAt(0.60, QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY))
538                anim.setKeyValueAt(0.70, QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY + (5 * float(i / 4.0))))
539                anim.setKeyValueAt(0.80, QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY))
540                anim.setKeyValueAt(0.90, QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY + (2 * float(i / 4.0))))
541                anim.setEndValue(QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY))
542                movieIn.append(anim)
543
544                # Create out-animation.
545                anim = DemoItemAnimation(item, DemoItemAnimation.ANIM_OUT)
546                anim.setHideOnFinished(True)
547                anim.setDuration(700 + (30 * i))
548                anim.setStartValue(QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY))
549                anim.setKeyValueAt(0.60, QPointF(xOffset, 600 - ih - ih))
550                anim.setKeyValueAt(0.65, QPointF(xOffset + 20, 600 - ih))
551                anim.setEndValue(QPointF(sw + iw, 600 - ih))
552                movieOut.append(anim)
553
554                # Create shake-animation.
555                anim = DemoItemAnimation(item)
556                anim.setDuration(700)
557                anim.setStartValue(QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY))
558                anim.setKeyValueAt(0.55, QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY - i*2.0))
559                anim.setKeyValueAt(0.70, QPointF(xOffset - 10, (i * ihp) + yOffset + Colors.contentStartY - i*1.5))
560                anim.setKeyValueAt(0.80, QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY - i*1.0))
561                anim.setKeyValueAt(0.90, QPointF(xOffset - 2, (i * ihp) + yOffset + Colors.contentStartY - i*0.5))
562                anim.setEndValue(QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY))
563                movieShake.append(anim)
564
565                # Create next-menu top-out-animation.
566                anim = DemoItemAnimation(item, DemoItemAnimation.ANIM_OUT)
567                anim.setHideOnFinished(True)
568                anim.setDuration(200 + (30 * i))
569                anim.setStartValue(QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY))
570                anim.setKeyValueAt(0.70, QPointF(xOffset, yOffset + Colors.contentStartY))
571                anim.setEndValue(QPointF(-iw, yOffset + Colors.contentStartY))
572                movieNextTopOut.append(anim)
573
574                # Create next-menu bottom-out-animation.
575                anim = DemoItemAnimation(item, DemoItemAnimation.ANIM_OUT)
576                anim.setHideOnFinished(True)
577                anim.setDuration(200 + (30 * i))
578                anim.setStartValue(QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY))
579                anim.setKeyValueAt(0.70, QPointF(xOffset, (maxExamples * ihp) + yOffset + Colors.contentStartY))
580                anim.setEndValue(QPointF(-iw, (maxExamples * ihp) + yOffset + Colors.contentStartY))
581                movieNextBottomOut.append(anim)
582
583                # Create next-menu top-in-animation.
584                anim = DemoItemAnimation(item, DemoItemAnimation.ANIM_IN)
585                anim.setDuration(700 - (30 * i))
586                anim.setStartValue(QPointF(-iw, yOffset + Colors.contentStartY))
587                anim.setKeyValueAt(0.30, QPointF(xOffset, yOffset + Colors.contentStartY))
588                anim.setEndValue(QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY))
589                movieNextTopIn.append(anim)
590
591                # Create next-menu bottom-in-animation.
592                reverse = maxExamples - i
593                anim = DemoItemAnimation(item, DemoItemAnimation.ANIM_IN)
594                anim.setDuration(1000 - (30 * reverse))
595                anim.setStartValue(QPointF(-iw, (maxExamples * ihp) + yOffset + Colors.contentStartY))
596                anim.setKeyValueAt(0.30, QPointF(xOffset, (maxExamples * ihp) + yOffset + Colors.contentStartY))
597                anim.setEndValue(QPointF(xOffset, (i * ihp) + yOffset + Colors.contentStartY))
598                movieNextBottomIn.append(anim)
599
600                i += 1
601                currentNode = self._next_element(currentNode)
602
603            if currentNode is not None and i == maxExamples:
604                # We need another menu, so register for 'more' and 'back'
605                # buttons.
606                menuIndex += 1
607                self.info.setdefault(currentMenu, {})['more'] = '%s -menu%d' % (name, menuIndex)
608                currentMenu = '%s -menu%d' % (name, menuIndex)
609                self.info.setdefault(currentMenu, {})['back'] = '%s -menu%d' % (name, menuIndex - 1)
610
611    def createLowLeftButton(self, label, type, movieIn, movieOut, movieShake, menuString=""):
612        button = TextButton(label, TextButton.RIGHT, type,
613                self.window.mainSceneRoot, TextButton.PANEL)
614        if menuString:
615            button.setMenuString(menuString)
616        button.setRecursiveVisible(False)
617        button.setZValue(10)
618
619        iw = button.sceneBoundingRect().width()
620        xOffset = 15
621
622        # Create in-animation.
623        buttonIn = DemoItemAnimation(button, DemoItemAnimation.ANIM_IN)
624        buttonIn.setDuration(1800)
625        buttonIn.setStartValue(QPointF(-iw, Colors.contentStartY + Colors.contentHeight - 35))
626        buttonIn.setKeyValueAt(0.5, QPointF(-iw, Colors.contentStartY + Colors.contentHeight - 35))
627        buttonIn.setKeyValueAt(0.7, QPointF(xOffset, Colors.contentStartY + Colors.contentHeight - 35))
628        buttonIn.setEndValue(QPointF(xOffset, Colors.contentStartY + Colors.contentHeight - 26))
629        movieIn.append(buttonIn)
630
631        # Create out-animation.
632        buttonOut = DemoItemAnimation(button, DemoItemAnimation.ANIM_OUT)
633        buttonOut.setHideOnFinished(True)
634        buttonOut.setDuration(400)
635        buttonOut.setStartValue(QPointF(xOffset, Colors.contentStartY + Colors.contentHeight - 26))
636        buttonOut.setEndValue(QPointF(-iw, Colors.contentStartY + Colors.contentHeight - 26))
637        movieOut.append(buttonOut)
638
639        if movieShake is not None:
640            shakeAnim = DemoItemAnimation(button, DemoItemAnimation.ANIM_UNSPECIFIED)
641            shakeAnim.setDuration(650)
642            shakeAnim.setStartValue(buttonIn.endValue())
643            shakeAnim.setKeyValueAt(0.60, buttonIn.endValue())
644            shakeAnim.setKeyValueAt(0.70, buttonIn.endValue() + QPointF(-3, 0))
645            shakeAnim.setKeyValueAt(0.80, buttonIn.endValue() + QPointF(2, 0))
646            shakeAnim.setKeyValueAt(0.90, buttonIn.endValue() + QPointF(-1, 0))
647            shakeAnim.setEndValue(buttonIn.endValue())
648            movieShake.append(shakeAnim)
649
650    def createLowRightButton(self, label, type, movieIn, movieOut, movieShake):
651        item = TextButton(label, TextButton.RIGHT, type,
652                self.window.mainSceneRoot, TextButton.PANEL)
653        item.setRecursiveVisible(False)
654        item.setZValue(10)
655
656        sw = self.window.scene.sceneRect().width()
657        xOffset = 70
658
659        # Create in-animation.
660        anim = DemoItemAnimation(item, DemoItemAnimation.ANIM_IN)
661        anim.setDuration(1800)
662        anim.setStartValue(QPointF(sw, Colors.contentStartY + Colors.contentHeight - 35))
663        anim.setKeyValueAt(0.5, QPointF(sw, Colors.contentStartY + Colors.contentHeight - 35))
664        anim.setKeyValueAt(0.7, QPointF(xOffset + 535, Colors.contentStartY + Colors.contentHeight - 35))
665        anim.setEndValue(QPointF(xOffset + 535, Colors.contentStartY + Colors.contentHeight - 26))
666        movieIn.append(anim)
667
668        # Create out-animation.
669        anim = DemoItemAnimation(item, DemoItemAnimation.ANIM_OUT)
670        anim.setHideOnFinished(True)
671        anim.setDuration(400)
672        anim.setStartValue(QPointF(xOffset + 535, Colors.contentStartY + Colors.contentHeight - 26))
673        anim.setEndValue(QPointF(sw, Colors.contentStartY + Colors.contentHeight - 26))
674        movieOut.append(anim)
675
676    def createLowRightLeafButton(self, label, xOffset, type, movieIn, movieOut, movieShake):
677        item = TextButton(label, TextButton.RIGHT, type,
678                self.window.mainSceneRoot, TextButton.PANEL)
679        item.setRecursiveVisible(False)
680        item.setZValue(10)
681
682        sw = self.window.scene.sceneRect().width()
683        sh = self.window.scene.sceneRect().height()
684
685        # Create in-animation.
686        anim = DemoItemAnimation(item, DemoItemAnimation.ANIM_IN)
687        anim.setDuration(1050)
688        anim.setStartValue(QPointF(sw, Colors.contentStartY + Colors.contentHeight - 35))
689        anim.setKeyValueAt(0.10, QPointF(sw, Colors.contentStartY + Colors.contentHeight - 35))
690        anim.setKeyValueAt(0.30, QPointF(xOffset, Colors.contentStartY + Colors.contentHeight - 35))
691        anim.setKeyValueAt(0.35, QPointF(xOffset + 30, Colors.contentStartY + Colors.contentHeight - 35))
692        anim.setKeyValueAt(0.40, QPointF(xOffset, Colors.contentStartY + Colors.contentHeight - 35))
693        anim.setKeyValueAt(0.45, QPointF(xOffset + 5, Colors.contentStartY + Colors.contentHeight - 35))
694        anim.setKeyValueAt(0.50, QPointF(xOffset, Colors.contentStartY + Colors.contentHeight - 35))
695        anim.setEndValue(QPointF(xOffset, Colors.contentStartY + Colors.contentHeight - 26))
696        movieIn.append(anim)
697
698        # Create out-animation.
699        anim = DemoItemAnimation(item, DemoItemAnimation.ANIM_OUT)
700        anim.setHideOnFinished(True)
701        anim.setDuration(300)
702        anim.setStartValue(QPointF(xOffset, Colors.contentStartY + Colors.contentHeight - 26))
703        anim.setEndValue(QPointF(xOffset, sh))
704        movieOut.append(anim)
705
706    def createInfo(self, item, name):
707        movie_in = self.score.insertMovie(name)
708        movie_out = self.score.insertMovie(name + ' -out')
709        item.setZValue(8)
710        item.setRecursiveVisible(False)
711
712        xOffset = 230.0
713        infoIn = DemoItemAnimation(item, DemoItemAnimation.ANIM_IN)
714        infoIn.setDuration(650)
715        infoIn.setStartValue(QPointF(self.window.scene.sceneRect().width(), Colors.contentStartY))
716        infoIn.setKeyValueAt(0.60, QPointF(xOffset, Colors.contentStartY))
717        infoIn.setKeyValueAt(0.70, QPointF(xOffset + 20, Colors.contentStartY))
718        infoIn.setKeyValueAt(0.80, QPointF(xOffset, Colors.contentStartY))
719        infoIn.setKeyValueAt(0.90, QPointF(xOffset + 7, Colors.contentStartY))
720        infoIn.setEndValue(QPointF(xOffset, Colors.contentStartY))
721        movie_in.append(infoIn)
722
723        infoOut = DemoItemAnimation(item, DemoItemAnimation.ANIM_OUT)
724        infoOut.setCurveShape(QEasingCurve.InQuad)
725        infoOut.setDuration(300)
726        infoOut.setHideOnFinished(True)
727        infoOut.setStartValue(QPointF(xOffset, Colors.contentStartY))
728        infoOut.setEndValue(QPointF(-600, Colors.contentStartY))
729        movie_out.append(infoOut)
730
731    def createTicker(self):
732        if Colors.noTicker:
733            return
734
735        movie_in = self.score.insertMovie('ticker')
736        movie_out = self.score.insertMovie('ticker -out')
737        movie_activate = self.score.insertMovie('ticker -activate')
738        movie_deactivate = self.score.insertMovie('ticker -deactivate')
739
740        self.ticker = ItemCircleAnimation()
741        self.ticker.setZValue(50)
742        self.ticker.hide()
743
744        # Move ticker in.
745        qtendpos = 485
746        qtPosY = 120
747        self.tickerInAnim = DemoItemAnimation(self.ticker,
748                DemoItemAnimation.ANIM_IN)
749        self.tickerInAnim.setDuration(500)
750        self.tickerInAnim.setStartValue(QPointF(self.window.scene.sceneRect().width(), Colors.contentStartY + qtPosY))
751        self.tickerInAnim.setKeyValueAt(0.60, QPointF(qtendpos, Colors.contentStartY + qtPosY))
752        self.tickerInAnim.setKeyValueAt(0.70, QPointF(qtendpos + 30, Colors.contentStartY + qtPosY))
753        self.tickerInAnim.setKeyValueAt(0.80, QPointF(qtendpos, Colors.contentStartY + qtPosY))
754        self.tickerInAnim.setKeyValueAt(0.90, QPointF(qtendpos + 5, Colors.contentStartY + qtPosY))
755        self.tickerInAnim.setEndValue(QPointF(qtendpos, Colors.contentStartY + qtPosY))
756        movie_in.append(self.tickerInAnim)
757
758        # Move ticker out.
759        qtOut = DemoItemAnimation(self.ticker, DemoItemAnimation.ANIM_OUT)
760        qtOut.setHideOnFinished(True)
761        qtOut.setDuration(500)
762        qtOut.setStartValue(QPointF(qtendpos, Colors.contentStartY + qtPosY))
763        qtOut.setEndValue(QPointF(self.window.scene.sceneRect().width() + 700, Colors.contentStartY + qtPosY))
764        movie_out.append(qtOut)
765
766        # Move ticker in on activate.
767        qtActivate = DemoItemAnimation(self.ticker)
768        qtActivate.setDuration(400)
769        qtActivate.setStartValue(QPointF(self.window.scene.sceneRect().width(), Colors.contentStartY + qtPosY))
770        qtActivate.setKeyValueAt(0.60, QPointF(qtendpos, Colors.contentStartY + qtPosY))
771        qtActivate.setKeyValueAt(0.70, QPointF(qtendpos + 30, Colors.contentStartY + qtPosY))
772        qtActivate.setKeyValueAt(0.80, QPointF(qtendpos, Colors.contentStartY + qtPosY))
773        qtActivate.setKeyValueAt(0.90, QPointF(qtendpos + 5, Colors.contentStartY + qtPosY))
774        qtActivate.setEndValue(QPointF(qtendpos, Colors.contentStartY + qtPosY))
775        movie_activate.append(qtActivate)
776
777        # Move ticker out on deactivate.
778        qtDeactivate = DemoItemAnimation(self.ticker)
779        qtDeactivate.setHideOnFinished(True)
780        qtDeactivate.setDuration(400)
781        qtDeactivate.setStartValue(QPointF(qtendpos, Colors.contentStartY + qtPosY))
782        qtDeactivate.setEndValue(QPointF(qtendpos, 800))
783        movie_deactivate.append(qtDeactivate)
784
785    def createUpnDownButtons(self):
786        xOffset = 15.0
787        yOffset = 450.0
788
789        self.upButton = TextButton("", TextButton.LEFT, MenuManager.UP,
790                self.window.mainSceneRoot, TextButton.UP)
791        self.upButton.prepare()
792        self.upButton.setPos(xOffset, yOffset)
793        self.upButton.setState(TextButton.DISABLED)
794
795        self.downButton = TextButton("", TextButton.LEFT, MenuManager.DOWN,
796                self.window.mainSceneRoot, TextButton.DOWN)
797        self.downButton.prepare()
798        self.downButton.setPos(xOffset + 10 + self.downButton.sceneBoundingRect().width(), yOffset)
799
800        movieShake = self.score.insertMovie('upndown -shake')
801
802        shakeAnim = DemoItemAnimation(self.upButton,
803                DemoItemAnimation.ANIM_UNSPECIFIED)
804        shakeAnim.setDuration(650)
805        shakeAnim.setStartValue(self.upButton.pos())
806        shakeAnim.setKeyValueAt(0.60, self.upButton.pos())
807        shakeAnim.setKeyValueAt(0.70, self.upButton.pos() + QPointF(-2, 0))
808        shakeAnim.setKeyValueAt(0.80, self.upButton.pos() + QPointF(1, 0))
809        shakeAnim.setKeyValueAt(0.90, self.upButton.pos() + QPointF(-1, 0))
810        shakeAnim.setEndValue(self.upButton.pos())
811        movieShake.append(shakeAnim)
812
813        shakeAnim = DemoItemAnimation(self.downButton,
814                DemoItemAnimation.ANIM_UNSPECIFIED)
815        shakeAnim.setDuration(650)
816        shakeAnim.setStartValue(self.downButton.pos())
817        shakeAnim.setKeyValueAt(0.60, self.downButton.pos())
818        shakeAnim.setKeyValueAt(0.70, self.downButton.pos() + QPointF(-5, 0))
819        shakeAnim.setKeyValueAt(0.80, self.downButton.pos() + QPointF(-3, 0))
820        shakeAnim.setKeyValueAt(0.90, self.downButton.pos() + QPointF(-1, 0))
821        shakeAnim.setEndValue(self.downButton.pos())
822        movieShake.append(shakeAnim)
823
824    def createBackButton(self):
825        backIn = self.score.insertMovie('back -in')
826        backOut = self.score.insertMovie('back -out')
827        backShake = self.score.insertMovie('back -shake')
828        self.createLowLeftButton("Back", MenuManager.ROOT, backIn, backOut,
829                backShake, Colors.rootMenuName)
830