Warning, /education/gcompris/src/activities/menu/Menu.qml is written in an unsupported language. File is not indexed.

0001 /* GCompris - Menu.qml
0002  *
0003  * SPDX-FileCopyrightText: 2014 Bruno Coudoin <bruno.coudoin@gcompris.net>
0004  *
0005  * Authors:
0006  *   Bruno Coudoin <bruno.coudoin@gcompris.net> (Qt Quick port)
0007  *
0008  *   SPDX-License-Identifier: GPL-3.0-or-later
0009  */
0010 import QtQuick 2.12
0011 import "../../core"
0012 import GCompris 1.0
0013 import QtGraphicalEffects 1.0
0014 import "qrc:/gcompris/src/core/core.js" as Core
0015 
0016 // For TextField
0017 import QtQuick.Controls 2.12
0018 
0019 /**
0020  * GCompris' top level menu screen.
0021  *
0022  * Displays a grid of available activities divided subdivided in activity
0023  * categories/sections.
0024  *
0025  * The visibility of the section row is toggled by the setting
0026  * ApplicationSettings.sectionVisible.
0027  *
0028  * The list of available activities depends on the following settings:
0029  *
0030  * * ApplicationSettings.showLockedActivities
0031  * * ApplicationSettings.filterLevelMin
0032  * * ApplicationSettings.filterLevelMax
0033  *
0034  * @inherit QtQuick.Item
0035  */
0036 ActivityBase {
0037     id: activity
0038     focus: true
0039     activityInfo: ActivityInfoTree.rootMenu
0040     isMenu: true
0041     onBack: {
0042         if (pageView.currentItem === activity) {
0043             // Restore focus that has been taken by the loaded activity
0044             focus = true
0045         } else {
0046             pageView.pop(to)
0047         }
0048 
0049     }
0050 
0051     onHome: {
0052         if(pageView.depth === 1 && !ApplicationSettings.isKioskMode) {
0053             Core.quit(activity);
0054         }
0055         else {
0056             pageView.popElement();
0057             // Restore focus that has been taken by the loaded activity
0058             if(pageView.currentItem === activity) {
0059                 focus = true;
0060                 if(ActivityInfoTree.startingActivity != "") {
0061                     Core.quit(activity);
0062                 }
0063             }
0064         }
0065     }
0066 
0067     enabled: ActivityInfoTree.startingActivity === ""
0068 
0069     onDisplayDialog: pageView.pushElement(dialog)
0070 
0071     onDisplayDialogs: {
0072         for (var i = 0; i < dialogs.length; i++) {
0073             pageView.pushElement(dialogs[i]);
0074         }
0075     }
0076 
0077     //when selecting a new language in config, we need to open the newVoicesDialog,
0078     //but only after the config page is closed, hence the need to have a signal calling a Timer calling the function
0079     signal newVoicesSignal
0080     onNewVoicesSignal: newVoicesDialogTimer.restart();
0081 
0082     Timer {
0083         id: newVoicesDialogTimer
0084         interval: 250
0085         onTriggered: newVoicesDialog();
0086     }
0087     function newVoicesDialog() {
0088         Core.showMessageDialog(activity,
0089                 qsTr("You selected a new locale, you need to restart GCompris to use it.<br/>Do you want to download the corresponding sound files now?"),
0090                 qsTr("Yes"), function() {
0091                     // yes -> start download
0092                     if (DownloadManager.downloadResource(
0093                             GCompris.VOICES,
0094                            {"locale": ApplicationInfo.getVoicesLocale(ApplicationSettings.locale)}))
0095                     var downloadDialog = Core.showDownloadDialog(pageView.currentItem, {});
0096                 },
0097                 qsTr("No"), null,
0098                 null
0099                 );
0100     }
0101 
0102     Connections {
0103         // At the launch of the application, box2d check is performed after we
0104         // first initialize the menu. This connection is to refresh
0105         // automatically the menu at start.
0106         target: ApplicationInfo
0107         onIsBox2DInstalledChanged: {
0108             ActivityInfoTree.filterByTag(activity.currentTag, currentCategory)
0109             ActivityInfoTree.filterEnabledActivities()
0110         }
0111     }
0112 
0113     // @cond INTERNAL_DOCS
0114     property string url: "qrc:/gcompris/src/activities/menu/resource/"
0115     property var sections: [
0116         {
0117             icon: activity.url + "all.svg",
0118             tag: "favorite"
0119         },
0120         {
0121             icon: activity.url + "computer.svg",
0122             tag: "computer"
0123         },
0124         {
0125             icon: activity.url + "discovery.svg",
0126             tag: "discovery",
0127             categories: [{ "logic": qsTr("Logic") },
0128                          { "arts": qsTr("Fine Arts") },
0129                          { "music": qsTr("Music") }
0130             ]
0131         },
0132         {
0133             icon: activity.url + "sciences.svg",
0134             tag: "sciences",
0135             categories: [{ "experiment": qsTr("Experiment") },
0136                          { "history": qsTr("History") },
0137                          { "geography": qsTr("Geography") }
0138             ]
0139         },
0140         {
0141             icon: activity.url + "fun.svg",
0142             tag: "fun"
0143         },
0144         {
0145             icon: activity.url + "math.svg",
0146             tag: "math",
0147             categories: [{ "numeration": qsTr("Numeration") },
0148                          { "arithmetic": qsTr("Arithmetic") },
0149                          { "measures": qsTr("Measures") }
0150                         ]
0151         },
0152         {
0153             icon: activity.url + "puzzle.svg",
0154             tag: "puzzle"
0155         },
0156         {
0157             icon: activity.url + "reading.svg",
0158             tag: "reading",
0159             categories: [{ "letters": qsTr("Letters") },
0160                          { "words": qsTr("Words") },
0161                          { "vocabulary": qsTr("Vocabulary") }
0162                         ]
0163         },
0164         {
0165             icon: activity.url + "strategy.svg",
0166             tag: "strategy"
0167         },
0168         {
0169             icon: activity.url + "search-icon.svg",
0170             tag: "search"
0171         }
0172     ]
0173     property string currentTag: sections[0].tag
0174     property var currentTagCategories: []
0175     property string currentCategory: ""
0176     /// @endcond
0177 
0178     property string clickMode: "play"
0179 
0180     signal startActivity(string activityName, int level)
0181 
0182     pageComponent: Image {
0183         id: background
0184         source: activity.url + "background.svg"
0185         sourceSize.width: width
0186         sourceSize.height: height
0187         height: main.height
0188         fillMode: Image.PreserveAspectCrop
0189 
0190         function loadActivity() {
0191             // @TODO init of item would be better in setsource but it crashes on Qt5.6
0192             // https://bugreports.qt.io/browse/QTBUG-49793
0193             activityLoader.item.audioVoices = audioVoices
0194             activityLoader.item.audioEffects = audioEffects
0195             activityLoader.item.loading = loading
0196 
0197             //take the focus away from textField before starting an activity
0198             searchTextField.focus = false
0199 
0200             pageView.pushElement(activityLoader.item)
0201             ActivityInfoTree.startingLevel = -1
0202         }
0203 
0204         Loader {
0205             id: activityLoader
0206             asynchronous: true
0207             onStatusChanged: {
0208                 if (status == Loader.Loading) {
0209                     loading.start();
0210                 } else if (status == Loader.Ready) {
0211                     loading.stop();
0212                     loadActivity();
0213                 } else if (status == Loader.Error) {
0214                     loading.stop();
0215                 }
0216             }
0217         }
0218 
0219         // Filters
0220         property bool horizontal: main.width >= main.height
0221         property int sectionIconWidth: Math.min(100 * ApplicationInfo.ratio, main.width / (sections.length + 1))
0222         property int sectionCellWidth: sectionIconWidth * 1.1
0223         property int categoriesHeight: currentCategory == "" ? 0 : sectionCellWidth - 2
0224 
0225         property var currentActiveGrid: activitiesGrid
0226         property bool keyboardMode: false
0227         Keys.onPressed: {
0228             if(loading.active) {
0229                 return;
0230             }
0231             // Ctrl-modifiers should never be handled by the search-field
0232             if (event.modifiers === Qt.ControlModifier) {
0233                 if (event.key === Qt.Key_S) {
0234                     // Ctrl+S toggle show / hide section
0235                     ApplicationSettings.sectionVisible = !ApplicationSettings.sectionVisible
0236                 }
0237             } else if(currentTag === "search") {
0238                 // forward to the virtual keyboard the pressed keys
0239                 if(event.key === Qt.Key_Backspace)
0240                     keyboard.keypress(keyboard.backspace);
0241                 else
0242                     keyboard.keypress(event.text);
0243             } else if(event.key === Qt.Key_Space && currentActiveGrid.currentItem) {
0244                 currentActiveGrid.currentItem.selectCurrentItem()
0245             }
0246         }
0247         Keys.onReleased: {
0248             keyboardMode = true
0249             event.accepted = false
0250         }
0251         Keys.onTabPressed: {
0252             if(currentActiveGrid == section) {
0253                 if(currentTagCategories && currentTagCategories.length != 0) {
0254                     currentActiveGrid = categoriesGrid;
0255                 }
0256                 else {
0257                     currentActiveGrid = activitiesGrid;
0258                 }
0259             }
0260             else if(currentActiveGrid == categoriesGrid) {
0261                 currentActiveGrid = activitiesGrid;
0262             }
0263             else {
0264                 currentActiveGrid = section;
0265             }
0266         }
0267         Keys.onEnterPressed: if(currentActiveGrid.currentItem && !loading.active) currentActiveGrid.currentItem.selectCurrentItem();
0268         Keys.onReturnPressed: if(currentActiveGrid.currentItem && !loading.active) currentActiveGrid.currentItem.selectCurrentItem();
0269         Keys.onRightPressed: if(currentActiveGrid.currentItem && !loading.active) currentActiveGrid.moveCurrentIndexRight();
0270         Keys.onLeftPressed: if(currentActiveGrid.currentItem && !loading.active) currentActiveGrid.moveCurrentIndexLeft();
0271         Keys.onDownPressed: if(currentActiveGrid.currentItem && !loading.active) currentActiveGrid.moveCurrentIndexDown();
0272         Keys.onUpPressed: if(currentActiveGrid.currentItem && !loading.active) currentActiveGrid.moveCurrentIndexUp();
0273 
0274         GridView {
0275             id: section
0276             model: sections
0277             x: ApplicationSettings.sectionVisible ? section.initialX : -sectionCellWidth
0278             y: ApplicationSettings.sectionVisible ? section.initialY : -sectionCellWidth
0279             visible: ApplicationSettings.sectionVisible
0280             cellWidth: sectionCellWidth
0281             cellHeight: sectionCellWidth
0282             interactive: false
0283             keyNavigationWraps: true
0284             property int initialX: 4
0285             property int initialY: 4
0286 
0287             Component {
0288                 id: sectionDelegate
0289                 Item {
0290                     id: backgroundSection
0291                     width: sectionCellWidth
0292                     height: sectionCellWidth
0293 
0294                     Image {
0295                         source: modelData.icon
0296                         sourceSize.height: sectionIconWidth
0297                         anchors.margins: 5
0298                         anchors.horizontalCenter: parent.horizontalCenter
0299                     }
0300 
0301                     ParticleSystemStarLoader {
0302                         id: particles
0303                         anchors.fill: backgroundSection
0304                         clip: false
0305                     }
0306                     MouseArea {
0307                         anchors.fill: backgroundSection
0308                         onClicked: {
0309                             selectCurrentItem()
0310                         }
0311                     }
0312 
0313                     function selectCurrentItem() {
0314                         section.currentIndex = index
0315                         activity.currentTag = modelData.tag
0316                         activity.currentTagCategories = modelData.categories
0317                         if(modelData.categories !== undefined) {
0318                             currentCategory = Object.keys(modelData.categories[0])[0];
0319                         }
0320                         else {
0321                             currentCategory = ""
0322                         }
0323                         particles.burst(10)
0324                         if(modelData.tag === "search") {
0325                             ActivityInfoTree.filterBySearch(searchTextField.text);
0326                         }
0327                         else {
0328                             ActivityInfoTree.filterByTag(modelData.tag, currentCategory)
0329                             ActivityInfoTree.filterEnabledActivities()
0330                         }
0331                     }
0332                 }
0333             }
0334             delegate: sectionDelegate
0335             highlight: Item {
0336                 width: sectionCellWidth
0337                 height: sectionCellWidth
0338 
0339                 Rectangle {
0340                     anchors.fill: parent
0341                     color:  "#5AFFFFFF"
0342                 }
0343                 Image {
0344                     source: "qrc:/gcompris/src/core/resource/button.svg"
0345                     anchors.fill: parent
0346                 }
0347                 Behavior on x { SpringAnimation { spring: 2; damping: 0.2 } }
0348                 Behavior on y { SpringAnimation { spring: 2; damping: 0.2 } }
0349             }
0350         }
0351 
0352         // Activities
0353         property int iconWidth: 120 * ApplicationInfo.ratio
0354         property int activityCellWidth:  activitiesGrid.width / Math.floor(activitiesGrid.width / iconWidth)
0355         property int activityCellHeight: iconWidth * 1.7
0356 
0357         Loader {
0358             id: warningOverlay
0359             anchors {
0360                 bottom: parent.bottom
0361                 right: parent.right
0362                 margins: 4
0363             }
0364             active: (ActivityInfoTree.menuTree.length === 0) && (currentTag === "favorite")
0365             sourceComponent: Item {
0366                 anchors.fill: parent
0367                 GCText {
0368                     id: instructionTxt
0369                     fontSize: smallSize
0370                     y: height * 0.2
0371                     x: (parent.width - width) / 2
0372                     z: 2
0373                     width: parent.width * 0.6
0374                     horizontalAlignment: Text.AlignHCenter
0375                     wrapMode: Text.WordWrap
0376                     font.weight: Font.DemiBold
0377                     color: 'white'
0378                     text: qsTr("Put your favorite activities here by clicking on the " +
0379                                "sun at the top right of that activity.")
0380                 }
0381                 Rectangle {
0382                     anchors.fill: instructionTxt
0383                     anchors.margins: -6
0384                     z: 1
0385                     opacity: 0.5
0386                     radius: 10
0387                     border.width: 2
0388                     border.color: "black"
0389                     gradient: Gradient {
0390                         GradientStop { position: 0.0; color: "#000" }
0391                         GradientStop { position: 0.9; color: "#666" }
0392                         GradientStop { position: 1.0; color: "#AAA" }
0393                     }
0394                 }
0395             }
0396         }
0397 
0398         GridView {
0399             id: categoriesGrid
0400             model: currentTagCategories
0401             topMargin: 5
0402             interactive: false
0403             keyNavigationWraps: true
0404             visible: activity.currentTag !== "search"
0405             cellWidth: currentTagCategories ? categoriesGrid.width / currentTagCategories.length : 0
0406             cellHeight: height
0407 
0408             delegate: GCButton {
0409                 id: button
0410                 selected: currentCategory === button.category
0411                 theme: "categories"
0412                 textSize: "regular"
0413                 rightIconSize: rightIcon.width + rightIcon.anchors.rightMargin
0414                 width: categoriesGrid.width / (currentTagCategories.length + 1)
0415                 height: categoriesGrid.cellHeight
0416                 text: modelData[category]
0417                 property string category: Object.keys(modelData)[0]
0418                 onClicked: {
0419                     selectCurrentItem()
0420                 }
0421 
0422                 function selectCurrentItem() {
0423                     categoriesGrid.currentIndex = index
0424                     currentCategory = Object.keys(modelData)[0]
0425                     ActivityInfoTree.filterByTag(currentTag, currentCategory)
0426                     ActivityInfoTree.filterEnabledActivities()
0427                 }
0428                 Image {
0429                     id: rightIcon
0430                     visible: horizontal
0431                     source: "qrc:/gcompris/src/activities/menu/resource/category-" + button.category + ".svg";
0432                     height: visible ? Math.round(parent.height * 0.8) : 0
0433                     sourceSize.height: height
0434                     width: height
0435                     anchors {
0436                         verticalCenter: parent.verticalCenter
0437                         right: parent.right
0438                         rightMargin: visible ? parent.height * 0.1 : 0
0439                     }
0440                 }
0441             }
0442             highlight: Rectangle {
0443                 z: 10
0444                 width: activityCellWidth - activitiesGrid.spacing
0445                 height: activityCellHeight - activitiesGrid.spacing
0446                 color:  "#00FFFFFF"
0447                 radius: 10
0448                 border.width: 5
0449                 border.color: "#FF87A6DD"
0450                 visible: true
0451                 Behavior on x { SpringAnimation { spring: 2; damping: 0.2 } }
0452                 Behavior on y { SpringAnimation { spring: 2; damping: 0.2 } }
0453             }
0454         }
0455 
0456         GridView {
0457             id: activitiesGrid
0458             anchors {
0459                 top: {
0460                     if(searchBar.visible)
0461                         return searchBar.bottom
0462                     else
0463                         return categoriesGrid.bottom
0464                 }
0465                 bottom: bar.top
0466                 margins: 4
0467                 topMargin: currentCategory == "" ? 4 : 10
0468             }
0469             width: background.width
0470             cellWidth: activityCellWidth
0471             cellHeight: activityCellHeight
0472             clip: true
0473             model: ActivityInfoTree.menuTree
0474             keyNavigationWraps: true
0475             property int spacing: 10
0476             // Needed to calculate the OpacityMask offset
0477             // If not using OpenGL, this value is not used, so we save the calculation and set it to 1
0478             property real hiddenBottom: ApplicationInfo.useOpenGL ? contentHeight - height - contentY : 1
0479 
0480             delegate: Item {
0481                 id: delegateItem
0482                 width: activityCellWidth - activitiesGrid.spacing
0483                 height: activityCellHeight - activitiesGrid.spacing
0484                 enabled: clickMode === "play" || (activityInfoTreeItem.hasConfig || activityInfoTreeItem.hasDataset)
0485                 property var activityInfoTreeItem: ActivityInfoTree.menuTree[index]
0486                 Rectangle {
0487                     id: activityBackground
0488                     width: parent.width
0489                     height: parent.height
0490                     anchors.horizontalCenter: parent.horizontalCenter
0491                     color: "white"
0492                     opacity: 0.5
0493                 }
0494                 Image {
0495                     id: activityIcon
0496                     source: "qrc:/gcompris/src/activities/" + icon
0497                     anchors.top: activityBackground.top
0498                     anchors.horizontalCenter: parent.horizontalCenter
0499                     width: iconWidth - activitiesGrid.spacing
0500                     height: width
0501                     sourceSize.width: width
0502                     fillMode: Image.PreserveAspectFit
0503                     anchors.margins: 5
0504                     opacity: delegateItem.enabled ? 1 : 0.5
0505 
0506                     Image {
0507                         id: minimalDifficultyIcon
0508                         source: "qrc:/gcompris/src/core/resource/difficulty" +
0509                                 activityInfoTreeItem.minimalDifficulty + ".svg"
0510                         anchors.top: parent.top
0511                         sourceSize.width: iconWidth * 0.15
0512                     }
0513                     Image {
0514                         id: iconSeparator
0515                         source: "qrc:/gcompris/src/core/resource/separator.svg"
0516                         visible: activityInfoTreeItem.minimalDifficulty !== activityInfoTreeItem.maximalDifficulty
0517                         anchors.top: parent.top
0518                         anchors.left: minimalDifficultyIcon.right
0519                         sourceSize.height: minimalDifficultyIcon.height
0520                     }
0521                     Image {
0522                         source: "qrc:/gcompris/src/core/resource/difficulty" +
0523                                 activityInfoTreeItem.maximalDifficulty + ".svg"
0524                         visible: iconSeparator.visible
0525                         anchors.top: parent.top
0526                         anchors.left: iconSeparator.right
0527                         sourceSize.width: minimalDifficultyIcon.width
0528                     }
0529                     Image {
0530                         anchors {
0531                             horizontalCenter: parent.horizontalCenter
0532                             top: parent.top
0533                         }
0534                         source: activityInfoTreeItem.createdInVersion > lastGCVersionRanCopy
0535                                 ? activity.url + "new.svg" : ""
0536                         sourceSize.width: 25 * ApplicationInfo.ratio
0537                     }
0538                     GCText {
0539                         id: title
0540                         anchors.top: parent.bottom
0541                         anchors.horizontalCenter: parent.horizontalCenter
0542                         horizontalAlignment: Text.AlignHCenter
0543                         width: activityBackground.width
0544                         height: activityBackground.height - activityIcon.height
0545                         fontSizeMode: Text.Fit
0546                         minimumPointSize: 10
0547                         fontSize: regularSize
0548                         elide: Text.ElideRight
0549                         wrapMode: Text.WordWrap
0550                         text: activityInfoTreeItem.title
0551                     }
0552                 }
0553                 ParticleSystemStarLoader {
0554                     id: particles
0555                     anchors.fill: activityBackground
0556                 }
0557                 MouseArea {
0558                     anchors.fill: activityBackground
0559                     onClicked: selectCurrentItem()
0560                 }
0561 
0562                 Image {
0563                     source: activity.url + (favorite ? "all.svg" : "all_disabled.svg");
0564                     anchors {
0565                         top: parent.top
0566                         right: parent.right
0567                         rightMargin: 4 * ApplicationInfo.ratio
0568                     }
0569                     sourceSize.width: iconWidth * 0.25
0570                     visible: ApplicationSettings.sectionVisible
0571                     MouseArea {
0572                         anchors.fill: parent
0573                         onClicked: favorite = !favorite
0574                     }
0575                 }
0576 
0577                 Loader {
0578                     id: chooseLevelLoader
0579                     active: false
0580                     onStatusChanged: {
0581                         if (status == Loader.Ready) {
0582                             displayDialog(item);
0583                         }
0584                     }
0585 
0586                     sourceComponent: DialogChooseLevel {
0587                         id: dialogChooseLevel
0588                         displayDatasetAtStart: hasDataset
0589                         currentActivity: activityInfoTreeItem
0590                         inMenu: true
0591 
0592                         onClose: {
0593                             home()
0594                         }
0595                         onSaveData: {
0596                             currentLevels = dialogChooseLevel.chosenLevels
0597                             ApplicationSettings.setCurrentLevels(name, currentLevels)
0598                         }
0599                         onStartActivity: {
0600                             clickMode = "play"
0601                             // immediately pop the Dialog to load the activity
0602                             // if we don't do it immediately the page is busy
0603                             // and it does not load the activity
0604                             pageView.pop(StackView.Immediate)
0605                             selectCurrentItem()
0606                         }
0607                     }
0608                 }
0609 
0610                 function selectCurrentItem() {
0611                     if(pageView.busy || !delegateItem.enabled)
0612                         return
0613 
0614                     if(clickMode === "play") {
0615                         particles.burst(50)
0616                         ActivityInfoTree.currentActivity = activityInfoTreeItem
0617                         activityLoader.setSource("qrc:/gcompris/src/activities/" + ActivityInfoTree.currentActivity.name,
0618                         {
0619                             'menu': activity,
0620                             'activityInfo': ActivityInfoTree.currentActivity,
0621                             'levelFolder': currentLevels
0622                         })
0623                         if (activityLoader.status == Loader.Ready) loadActivity()
0624                     }
0625                     else {
0626                         // Display configuration
0627                         chooseLevelLoader.active = false;
0628                         chooseLevelLoader.active = true;
0629                     }
0630                     activitiesGrid.currentIndex = index
0631                 }
0632             }
0633             highlight: Rectangle {
0634                 width: activityCellWidth - activitiesGrid.spacing
0635                 height: activityCellHeight - activitiesGrid.spacing
0636                 color:  "#AAFFFFFF"
0637                 border.width: 3
0638                 border.color: "black"
0639                 visible: background.keyboardMode
0640                 Behavior on x { SpringAnimation { spring: 2; damping: 0.2 } }
0641                 Behavior on y { SpringAnimation { spring: 2; damping: 0.2 } }
0642             }
0643 
0644             Rectangle {
0645                 id: activitiesMask
0646                 visible: false
0647                 anchors.fill: activitiesGrid
0648                 // Dynamic position of the gradient used for OpacityMask
0649                 // If the hidden bottom part of the grid is > to the maximum height of the gradient,
0650                 // we use the maximum height.
0651                 // Else we set the gradient start position proportionnally to the hidden bottom part,
0652                 // until it disappears.
0653                 // And if not using OpenGL, the mask is disabled, so we save the calculation and set it to 1
0654                 property real gradientStartValue:
0655                     ApplicationInfo.useOpenGL ?
0656                     (activitiesGrid.hiddenBottom > activitiesGrid.height * 0.08 ?
0657                         0.92 : 1 - (activitiesGrid.hiddenBottom / activitiesGrid.height)) :
0658                         1
0659                 gradient: Gradient {
0660                   GradientStop { position: 0.0; color: "#FFFFFFFF" }
0661                   GradientStop { position: activitiesMask.gradientStartValue; color: "#FFFFFFFF" }
0662                   GradientStop { position: activitiesMask.gradientStartValue + 0.04; color: "#00FFFFFF"}
0663                 }
0664             }
0665             layer.enabled: ApplicationInfo.useOpenGL
0666             layer.effect: OpacityMask {
0667                 id: activitiesOpacity
0668                 source: activitiesGrid
0669                 maskSource: activitiesMask
0670                 anchors.fill: activitiesGrid
0671             }
0672         }
0673 
0674         // The scroll buttons
0675         GCButtonScroll {
0676             visible: !ApplicationInfo.useOpenGL
0677             anchors.right: parent.right
0678             anchors.rightMargin: 5 * ApplicationInfo.ratio
0679             anchors.bottom: activitiesGrid.bottom
0680             anchors.bottomMargin: 30 * ApplicationInfo.ratio
0681             onUp: activitiesGrid.flick(0, 1127)
0682             onDown: activitiesGrid.flick(0, -1127)
0683             upVisible: activitiesGrid.atYBeginning ? false : true
0684             downVisible: activitiesGrid.atYEnd ? false : true
0685         }
0686 
0687         Rectangle {
0688             id: categories
0689             height: searchTextField.height
0690             visible: sections[activity.currentTag] === "search"
0691         }
0692 
0693         Rectangle {
0694             id: searchBar
0695             visible: activity.currentTag === "search"
0696             radius: 5 * ApplicationInfo.ratio
0697             border.width: 2
0698             border.color: "#80000000"
0699             gradient: Gradient {
0700                 GradientStop { position: 0.3; color: "#A0CCCCCC" }
0701                 GradientStop { position: 0.9; color: "#A0EEEEEE" }
0702                 GradientStop { position: 1; color: "#A0FFFFFF" }
0703             }
0704 
0705             Connections {
0706                 // On mobile with GCompris' virtual keyboard activated:
0707                 // Force invisibility of Androids virtual keyboard:
0708                 target: (ApplicationInfo.isMobile && activity.currentTag === "search"
0709                          && ApplicationSettings.isVirtualKeyboard) ? Qt.inputMethod : null
0710                 onVisibleChanged: {
0711                     if (ApplicationSettings.isVirtualKeyboard && visible)
0712                         Qt.inputMethod.hide();
0713                 }
0714                 onAnimatingChanged: {
0715                     // note: seems to be never fired!
0716                     if (ApplicationSettings.isVirtualKeyboard && Qt.inputMethod.visible)
0717                         Qt.inputMethod.hide();
0718                 }
0719             }
0720 
0721             Connections {
0722                 target: activity
0723                 onCurrentTagChanged: {
0724                     if (activity.currentTag === 'search') {
0725                         if(ApplicationSettings.isVirtualKeyboard && !keyboard.isPopulated) {
0726                             keyboard.populate();
0727                             keyboard.isPopulated = true;
0728                         }
0729                         searchTextField.focus = true;
0730                     } else
0731                         activity.focus = true;
0732                 }
0733 
0734                 onStartActivity: {
0735                     ActivityInfoTree.setCurrentActivityFromName(activityName)
0736                     var currentLevels = ApplicationSettings.currentLevels(ActivityInfoTree.currentActivity.name)
0737                     activityLoader.setSource("qrc:/gcompris/src/activities/" + ActivityInfoTree.currentActivity.name,
0738                     {
0739                         'menu': activity,
0740                         'activityInfo': ActivityInfoTree.currentActivity,
0741                         'levelFolder': currentLevels
0742                     })
0743                     if (activityLoader.status == Loader.Ready) loadActivity()
0744                 }
0745             }
0746 
0747             TextField {
0748                 id: searchTextField
0749                 width: parent.width
0750                 height: parent.height
0751                 color: "black"
0752                 font.pixelSize: height * 0.5
0753                 font.bold: true
0754                 opacity: 0.5
0755                 horizontalAlignment: TextInput.AlignHCenter
0756                 verticalAlignment: TextInput.AlignVCenter
0757                 font.family: GCSingletonFontLoader.fontLoader.name
0758                 inputMethodHints: Qt.ImhNoPredictiveText
0759                 // Note: we give focus to the textfield also in case
0760                 // isMobile && !ApplicationSettings.isVirtualKeyboard
0761                 // in conjunction with auto-hiding the inputMethod to always get
0762                 // an input-cursor:
0763                 activeFocusOnPress: true //ApplicationInfo.isMobile ? !ApplicationSettings.isVirtualKeyboard : true
0764 
0765                 background: Rectangle {
0766                     opacity: 0
0767                 }
0768 
0769                 Keys.onReturnPressed: {
0770                     if (ApplicationInfo.isMobile && !ApplicationSettings.isVirtualKeyboard)
0771                         Qt.inputMethod.hide();
0772                     activity.focus = true;
0773                 }
0774 
0775                 onEditingFinished: {
0776                     if (ApplicationInfo.isMobile && !ApplicationSettings.isVirtualKeyboard)
0777                         Qt.inputMethod.hide();
0778                     activity.focus = true;
0779                 }
0780 
0781                 placeholderText: qsTr("Search specific activities")
0782                 onTextChanged: searchTimer.restart();
0783             }
0784         }
0785 
0786         //timer to workaround some weird Type errors when typing too fast in the search field
0787         Timer {
0788             id: searchTimer
0789             interval: 500
0790             onTriggered: ActivityInfoTree.filterBySearch(searchTextField.text);
0791         }
0792 
0793         Rectangle {
0794             id: activityConfigTextBar
0795             height: activitySettingsLabel.height
0796             visible: clickMode === "activityConfig"
0797             anchors {
0798                 bottom: bar.top
0799                 bottomMargin: height * 0.3
0800             }
0801             radius: 10
0802             border.width: height * 0.05
0803             border.color: "#8b66b2"
0804             color: "#eeeeee"
0805 
0806             GCText {
0807                 id: activitySettingsLabel
0808                 text: qsTr("Activity Settings")
0809                 fontSizeMode: Text.Fit
0810                 visible: parent.visible
0811                 width: parent.width
0812                 anchors.verticalCenter: parent.verticalCenter
0813                 horizontalAlignment: Text.AlignHCenter
0814                 color: "#232323"
0815             }
0816         }
0817 
0818         states: [
0819             State {
0820                 name: "horizontalState"; when: horizontal === true
0821                 PropertyChanges {
0822                     target: background
0823                     sectionIconWidth: Math.min(100 * ApplicationInfo.ratio, main.width / (sections.length + 1))
0824                 }
0825                 PropertyChanges {
0826                     target: section
0827                     width: main.width
0828                     height: sectionCellWidth
0829                 }
0830                 PropertyChanges {
0831                     target: categoriesGrid
0832                     width: main.width
0833                     height: categoriesHeight * 0.5
0834                     x: currentTagCategories ? categoriesGrid.width / (4 * (currentTagCategories.length + 1)) : 0
0835                 }
0836                 PropertyChanges {
0837                     target: activitiesGrid
0838                     width: background.width
0839                 }
0840                 PropertyChanges {
0841                     target: categories
0842                     width: background.width
0843                 }
0844                 PropertyChanges {
0845                     target: searchBar
0846                     width: background.width * 0.5
0847                     height: sectionCellWidth * 0.5
0848                     anchors.topMargin: 0
0849                     anchors.bottomMargin: 0
0850                 }
0851                 PropertyChanges {
0852                     target: activityConfigTextBar
0853                     width: background.width * 0.5
0854                 }
0855                 AnchorChanges {
0856                     target: warningOverlay
0857                     anchors.top: section.bottom
0858                     anchors.left: background.left
0859                 }
0860                 AnchorChanges {
0861                     target: categoriesGrid
0862                     anchors.top: section.bottom
0863                 }
0864                 AnchorChanges {
0865                     target: activitiesGrid
0866                     anchors.left: background.left
0867                 }
0868                 AnchorChanges {
0869                     target: categories
0870                     anchors.top: section.bottom
0871                     anchors.left: undefined
0872                 }
0873                 AnchorChanges {
0874                     target: searchBar
0875                     anchors.top: section.bottom
0876                     anchors.left: undefined
0877                     anchors.horizontalCenter: background.horizontalCenter
0878                 }
0879                 AnchorChanges {
0880                     target: activityConfigTextBar
0881                     anchors.left: undefined
0882                     anchors.horizontalCenter: background.horizontalCenter
0883                 }
0884             },
0885             State {
0886                 name: "verticalState"; when: horizontal === false
0887                 PropertyChanges {
0888                     target: background
0889                     sectionIconWidth: Math.min(100 * ApplicationInfo.ratio, (background.height - bar.height) / (sections.length + 1))
0890                 }
0891                 PropertyChanges {
0892                     target: section
0893                     width: sectionCellWidth
0894                     height: main.height - bar.height
0895                 }
0896                 PropertyChanges {
0897                     target: categoriesGrid
0898                     width: main.width - section.width
0899                     height: categoriesHeight
0900                     x: currentTagCategories ? categoriesGrid.width / (4 * (currentTagCategories.length + 1)) + section.width : 0
0901                 }
0902                 PropertyChanges {
0903                     target: activitiesGrid
0904                     width: background.width - sectionCellWidth
0905                 }
0906                 PropertyChanges {
0907                     target: categories
0908                     width: background.width - (section.width + 10)
0909                 }
0910                 PropertyChanges {
0911                     target: searchBar
0912                     width: background.width - (section.width + 10)
0913                     height: sectionCellWidth
0914                     anchors.topMargin: 4
0915                     anchors.bottomMargin: 4
0916                 }
0917                 PropertyChanges {
0918                     target: activityConfigTextBar
0919                     width: background.width - (section.width + 10)
0920                 }
0921                 AnchorChanges {
0922                     target: warningOverlay
0923                     anchors.top: background.top
0924                     anchors.left: section.right
0925                 }
0926                 AnchorChanges {
0927                     target: categoriesGrid
0928                     anchors.top: background.top
0929                 }
0930                 AnchorChanges {
0931                     target: activitiesGrid
0932                     anchors.left: section.right
0933                 }
0934                 AnchorChanges {
0935                     target: categories
0936                     anchors.top: categoriesGrid.top
0937                     anchors.left: section.right
0938                 }
0939                 AnchorChanges {
0940                     target: searchBar
0941                     anchors.top: background.top
0942                     anchors.left: section.right
0943                     anchors.horizontalCenter: undefined
0944                 }
0945                 AnchorChanges {
0946                     target: activityConfigTextBar
0947                     anchors.left: section.right
0948                     anchors.horizontalCenter: undefined
0949                 }
0950             },
0951             State {
0952                 name: "verticalWithSearch"; when: horizontal === false && activity.currentTag === "search" && ApplicationSettings.isVirtualKeyboard
0953                 PropertyChanges {
0954                     target: background
0955                     sectionIconWidth: Math.min(100 * ApplicationInfo.ratio, (background.height - (bar.height+keyboard.height)) / (sections.length + 1))
0956                 }
0957                 PropertyChanges {
0958                     target: section
0959                     width: main.width
0960                     height: sectionCellWidth (sections.length + 1)
0961                 }
0962                 PropertyChanges {
0963                     target: categoriesGrid
0964                     width: main.width - section.width
0965                     height: categoriesHeight
0966                     x: currentTagCategories ? categoriesGrid.width / (4 * (currentTagCategories.length + 1)) + section.width : 0
0967                 }
0968                 PropertyChanges {
0969                     target: activitiesGrid
0970                     width: background.width - sectionCellWidth
0971                 }
0972                 PropertyChanges {
0973                     target: categories
0974                     width: background.width - (section.width + 10)
0975                 }
0976                 PropertyChanges {
0977                     target: searchBar
0978                     width: background.width - (section.width + 10)
0979                     height: sectionCellWidth
0980                     anchors.topMargin: 4
0981                     anchors.bottomMargin: 4
0982                 }
0983                 PropertyChanges {
0984                     target: activityConfigTextBar
0985                     width: background.width - (section.width + 10)
0986                 }
0987                 AnchorChanges {
0988                     target: warningOverlay
0989                     anchors.top: background.top
0990                     anchors.left: section.right
0991                 }
0992                 AnchorChanges {
0993                     target: categoriesGrid
0994                     anchors.top: background.top
0995                 }
0996                 AnchorChanges {
0997                     target: activitiesGrid
0998                     anchors.left: section.right
0999                 }
1000                 AnchorChanges {
1001                     target: categories
1002                     anchors.top: categoriesGrid.top
1003                     anchors.left: section.right
1004                 }
1005                 AnchorChanges {
1006                     target: searchBar
1007                     anchors.top: background.top
1008                     anchors.left: section.right
1009                     anchors.horizontalCenter: undefined
1010                 }
1011             }
1012         ]
1013 
1014         VirtualKeyboard {
1015             id: keyboard
1016             property bool isPopulated: false
1017             readonly property var letter: ActivityInfoTree.characters
1018             width: parent.width
1019             visible: activity.currentTag === "search" && ApplicationSettings.isVirtualKeyboard
1020             anchors.bottom: parent.bottom
1021             anchors.horizontalCenter: parent.horizontalCenter
1022             onKeypress: {
1023                 var textArray = searchTextField.text.split("");
1024                 var cursorPosition = searchTextField.cursorPosition
1025                 if(text == keyboard.backspace) {
1026                     --cursorPosition;
1027                     textArray.splice(cursorPosition, 1);
1028                 }
1029                 else if(text == keyboard.space) {
1030                     textArray.splice(cursorPosition, 0, " ");
1031                     ++cursorPosition;
1032                 }
1033                 else {
1034                     textArray.splice(cursorPosition, 0, text);
1035                     ++cursorPosition;
1036                 }
1037                 searchTextField.text = textArray.join("");
1038                 searchTextField.cursorPosition = cursorPosition;
1039             }
1040             function populate() {
1041                var tmplayout = [];
1042                var row = 0;
1043                var offset = 0;
1044                var cols;
1045                while(offset < letter.length-1) {
1046                    if(letter.length <= 100) {
1047                        cols = Math.ceil((letter.length-offset) / (3 - row));
1048                    }
1049                    else {
1050                        cols = background.horizontal ? (Math.ceil((letter.length-offset) / (15 - row)))
1051                                                        :(Math.ceil((letter.length-offset) / (22 - row)))
1052                        if(row === 0) {
1053                            tmplayout[row] = [];
1054                            tmplayout[row].push({ label: keyboard.backspace });
1055                            tmplayout[row].push({ label: keyboard.space });
1056                            row ++;
1057                        }
1058                    }
1059 
1060                    tmplayout[row] = [];
1061                    for (var j = 0; j < cols; j++)
1062                        tmplayout[row][j] = { label: letter[j+offset] };
1063                    offset += j;
1064                    row ++;
1065                }
1066                if(letter.length <= 100) {
1067                    tmplayout[0].push({ label: keyboard.space });
1068                    tmplayout[row-1].push({ label: keyboard.backspace });
1069                }
1070                keyboard.layout = tmplayout
1071            }
1072         }
1073 
1074         Bar {
1075             id: bar
1076             // No exit button on mobile, UI Guidelines prohibits it
1077             content: BarEnumContent {
1078                 value: help | config | activityConfig | about | (ApplicationInfo.isMobile ? 0 : exit)
1079             }
1080             anchors.bottom: keyboard.visible ? keyboard.top : parent.bottom
1081             onAboutClicked: {
1082                 searchTextField.focus = false
1083                 displayDialog(dialogAbout)
1084             }
1085 
1086             onHelpClicked: {
1087                 searchTextField.focus = false
1088                 displayDialog(dialogHelp)
1089             }
1090 
1091             onActivityConfigClicked: {
1092                 if(clickMode == "play") {
1093                     clickMode = "activityConfig"
1094                 }
1095                 else {
1096                     clickMode = "play"
1097                 }
1098             }
1099 
1100             onConfigClicked: {
1101                 searchTextField.focus = false
1102                 dialogActivityConfig.active = true
1103                 dialogActivityConfig.loader.item.loadFromConfig()
1104                 displayDialog(dialogActivityConfig)
1105             }
1106         }
1107 
1108         DialogAbout {
1109             id: dialogAbout
1110             onClose: home()
1111         }
1112         DialogHelp {
1113             id: dialogHelp
1114             onClose: home()
1115             activityInfo: ActivityInfoTree.rootMenu
1116         }
1117 
1118         DialogActivityConfig {
1119             id: dialogActivityConfig
1120             currentActivity: activity
1121 
1122             content: Component {
1123                 ConfigurationItem {
1124                     id: configItem
1125                     parentActivity: activity
1126                     width: dialogActivityConfig.width - 50 * ApplicationInfo.ratio
1127                 }
1128             }
1129 
1130             onSaveData: {
1131                 dialogActivityConfig.configItem.save()
1132             }
1133             onClose: {
1134                 if(activity.currentTag != "search") {
1135                     ActivityInfoTree.filterByTag(activity.currentTag, currentCategory)
1136                     ActivityInfoTree.filterEnabledActivities()
1137                 } else
1138                     ActivityInfoTree.filterBySearch(searchTextField.text);
1139 
1140                 var filteredBackgroundMusic = dialogActivityConfig.configItem.filteredBackgroundMusic
1141                 backgroundMusic.clearQueue()
1142                 var allBackgroundMusic = dialogActivityConfig.configItem.allBackgroundMusic
1143                 /**
1144                  * 1. If the current playing background music is in new filtered playlist too, continue playing it and append all the next filtered musics to backgroundMusic element.
1145                  * 2. Else, stop the current music, find the filtered music which comes just after it, and append all the further musics after it.
1146                  */
1147                 var backgroundMusicSource = String(backgroundMusic.source)
1148                 var backgroundMusicName = dialogActivityConfig.configItem.extractMusicNameFromPath(backgroundMusicSource) + backgroundMusicSource.slice(backgroundMusicSource.lastIndexOf('.'), backgroundMusicSource.length)
1149                 var nextMusicIndex = filteredBackgroundMusic.indexOf(backgroundMusicName)
1150                 if(nextMusicIndex !== -1) {
1151                     nextMusicIndex++
1152                     while(nextMusicIndex < filteredBackgroundMusic.length)
1153                         backgroundMusic.append(ApplicationInfo.getAudioFilePath("backgroundMusic/" + filteredBackgroundMusic[nextMusicIndex++]))
1154                 }
1155                 else {
1156                     nextMusicIndex = allBackgroundMusic.indexOf(backgroundMusicName) + 1
1157                     while(nextMusicIndex < allBackgroundMusic.length) {
1158                         if(filteredBackgroundMusic.indexOf(allBackgroundMusic[nextMusicIndex]) !== -1) {
1159                             nextMusicIndex = filteredBackgroundMusic.indexOf(allBackgroundMusic[nextMusicIndex])
1160                             break
1161                         }
1162                         nextMusicIndex++
1163                     }
1164 
1165                     while(nextMusicIndex < filteredBackgroundMusic.length)
1166                         backgroundMusic.append(ApplicationInfo.getAudioFilePath("backgroundMusic/" + filteredBackgroundMusic[nextMusicIndex++]))
1167                     backgroundMusic.nextAudio()
1168                 }
1169                 home()
1170             }
1171 
1172             BackgroundMusicList {
1173                 id: backgroundMusicList
1174                 onClose: {
1175                     visible = false
1176                     dialogActivityConfig.configItem.visible = true
1177                 }
1178             }
1179         }
1180     }
1181     // splash screen when using --launch <activity> option
1182     Rectangle {
1183         visible: ActivityInfoTree.startingActivity != ""
1184         anchors.fill: parent
1185         color: "#16B8EA"
1186         Image {
1187             source: "qrc:/gcompris/src/core/resource/gcompris-logo-full.svg"
1188             anchors.bottom: parent.bottom
1189             anchors.right: parent.right
1190             anchors.margins: 20 * ApplicationInfo.ratio
1191             width: parent.width * 0.3
1192             sourceSize.width: width
1193             fillMode: Image.PreserveAspectFit
1194         }
1195     }
1196 }