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 }