Warning, /education/gcompris/src/core/GCCreationHandler.qml is written in an unsupported language. File is not indexed.

0001 /* GCompris - GCCreationHandler.qml
0002  *
0003  * SPDX-FileCopyrightText: 2018 Aman Kumar Gupta <gupta2140@gmail.com>
0004  *
0005  * Authors:
0006  *   Aman Kumar Gupta <gupta2140@gmail.com>
0007  *
0008  *   SPDX-License-Identifier: GPL-3.0-or-later
0009  */
0010 
0011 import QtQuick 2.12
0012 import GCompris 1.0
0013 // TextField
0014 import QtQuick.Controls 2.12
0015 import "qrc:/gcompris/src/core/core.js" as Core
0016 
0017 Rectangle {
0018     id: creationHandler
0019 
0020     width: parent.width
0021     height: parent.height
0022     color: "#ABCDEF"
0023     border.color: "white"
0024     border.width: 2
0025     radius: 20
0026     visible: false
0027     z: 2000
0028     focus: true
0029 
0030     onVisibleChanged: {
0031         if(visible) {
0032             creationHandler.forceActiveFocus();
0033             parent.Keys.enabled = false;
0034             dialogOpened = false;
0035             keyboardCreation.populate();
0036         } else {
0037             parent.Keys.enabled = true;
0038             parent.forceActiveFocus();
0039         }
0040     }
0041 
0042     signal close
0043     signal fileLoaded(var data, var filePath)
0044 
0045     onClose: {
0046         fileNameInput.focus = false
0047         fileNameInput.text = ""
0048         visible = false
0049         viewContainer.selectedFileIndex = -1
0050         creationsList.flick(0, 1400)
0051     }
0052 
0053     MouseArea {
0054         anchors.fill: parent
0055         onClicked: viewContainer.selectedFileIndex = -1
0056     }
0057 
0058 
0059     property var dataToSave
0060     property bool isSaveMode: false
0061     property bool dialogOpened: false
0062     readonly property string activityName: ActivityInfoTree.currentActivity.name.split('/')[0]
0063     readonly property string sharedDirectoryPath: ApplicationSettings.userDataPath + "/" + activityName + "/"
0064     readonly property string fileSavePath: "file://" + sharedDirectoryPath + '/' + fileNameInput.text + ".json"
0065 
0066     ListModel {
0067         id: fileNames
0068     }
0069 
0070     Directory {
0071         id: directory
0072     }
0073 
0074     File {
0075         id: file
0076         onError: console.error("File error: " + msg);
0077     }
0078 
0079     JsonParser {
0080         id: parser
0081         onError: console.error("Error parsing JSON: " + msg);
0082     }
0083 
0084     Timer {
0085         id: restoreFocusTimer
0086         interval: 500
0087         onTriggered: {
0088             dialogOpened = false;
0089         }
0090     }
0091 
0092     Timer {
0093         id: writeDataTimer
0094         interval: 500
0095         onTriggered:  {
0096             writeData();
0097         }
0098     }
0099 
0100     function refreshWindow(filterText) {
0101         var pathExists = file.exists(sharedDirectoryPath)
0102         if(!pathExists)
0103             return
0104 
0105         fileNames.clear()
0106 
0107         var files = directory.getFiles(sharedDirectoryPath)
0108         for(var i = 0; i < files.length; i++) {
0109             if(filterText === undefined || filterText === "" ||
0110               (files[i].toLowerCase()).indexOf(filterText) !== -1)
0111                 fileNames.append({ "name": files[i] })
0112         }
0113     }
0114 
0115     function loadWindow() {
0116         creationHandler.visible = true
0117         creationHandler.isSaveMode = false
0118         fileNameInput.forceActiveFocus()
0119         refreshWindow()
0120     }
0121 
0122     function loadFile(fileName) {
0123         var filePath = "file://" + sharedDirectoryPath + fileNames.get(viewContainer.selectedFileIndex).name
0124         var data = parser.parseFromUrl(filePath)
0125         creationHandler.fileLoaded(data, filePath)
0126         creationHandler.close()
0127     }
0128 
0129     function deleteFile() {
0130         dialogOpened = true;
0131         var filePath = "file://" + sharedDirectoryPath + fileNames.get(viewContainer.selectedFileIndex).name
0132         if(file.rmpath(filePath)) {
0133             Core.showMessageDialog(creationHandler,
0134                                    qsTr("%1 deleted successfully!").arg(filePath),
0135                                    qsTr("Ok"), null, "", null, function() { restoreFocusTimer.restart(); });
0136         }
0137         else {
0138             Core.showMessageDialog(creationHandler,
0139                                    qsTr("Unable to delete %1!").arg(filePath),
0140                                    qsTr("Ok"), null, "", null, function() { restoreFocusTimer.restart(); });
0141         }
0142 
0143         viewContainer.selectedFileIndex = -1
0144         refreshWindow()
0145     }
0146 
0147     function saveWindow(data) {
0148         creationHandler.visible = true
0149         creationHandler.isSaveMode = true
0150         creationHandler.dataToSave = data
0151         fileNameInput.forceActiveFocus()
0152         refreshWindow()
0153     }
0154 
0155     function saveFile() {
0156         if(activityName === "" || fileNameInput.text === "")
0157             return
0158 
0159         if(!file.exists(sharedDirectoryPath))
0160             file.mkpath(sharedDirectoryPath)
0161 
0162         if(file.exists(fileSavePath)) {
0163             replaceFileDialog();
0164         }
0165         else
0166             writeData()
0167     }
0168 
0169     function replaceFileDialog() {
0170         dialogOpened = true;
0171         Core.showMessageDialog(creationHandler,
0172                                qsTr("A file with this name already exists. Do you want to replace it?"),
0173                                qsTr("Yes"), function() { writeDataTimer.restart(); }, qsTr("No"), function() { restoreFocusTimer.restart(); }, null);
0174     }
0175 
0176     function writeData() {
0177         dialogOpened = true;
0178         file.write(JSON.stringify(creationHandler.dataToSave), fileSavePath);
0179         Core.showMessageDialog(creationHandler,
0180                                qsTr("Saved successfully!"),
0181                                qsTr("Ok"), null, "", null, function() { restoreFocusTimer.restart(); });
0182         refreshWindow();
0183     }
0184 
0185     function searchFiles() {
0186         viewContainer.selectedFileIndex = -1
0187         refreshWindow(fileNameInput.text.toLowerCase())
0188     }
0189 
0190     TextField {
0191         id: fileNameInput
0192         width: parent.width / 2
0193         font.pointSize: NaN
0194         font.pixelSize: height * 0.6
0195         height: cancelButton.height * 0.5
0196         anchors.verticalCenter: saveButton.verticalCenter
0197         anchors.left: parent.left
0198         anchors.leftMargin: 20
0199         verticalAlignment: TextInput.AlignVCenter
0200         selectByMouse: true
0201         maximumLength: 15
0202         placeholderText: creationHandler.isSaveMode ? qsTr("Enter file name") : qsTr("Search")
0203         onTextChanged: {
0204             if(!creationHandler.isSaveMode)
0205                 searchFiles()
0206         }
0207         color: "black"
0208         background: Rectangle {
0209             border.color: "black"
0210             border.width: 1
0211             radius: fileNameInput.height / 4
0212         }
0213     }
0214 
0215     GCButton {
0216         id: saveButton
0217         height: fileNameInput.height
0218         visible: creationHandler.isSaveMode
0219         text: qsTr("Save")
0220         theme: "highContrast"
0221         anchors.verticalCenter: cancelButton.verticalCenter
0222         anchors.left: fileNameInput.right
0223         anchors.right: cancelButton.left
0224         anchors.margins: 20 * ApplicationInfo.ratio
0225         onClicked: saveFile()
0226     }
0227 
0228     property real cellWidth: 50 * ApplicationInfo.ratio
0229     property real cellHeight: cellWidth * 1.3
0230 
0231     Rectangle {
0232         id: viewContainer
0233         anchors.top: cancelButton.bottom
0234         anchors.bottom: buttonRow.top
0235         anchors.margins: 10 * ApplicationInfo.ratio
0236         border.color: "black"
0237         border.width: 2
0238         radius: 20
0239         anchors.left: parent.left
0240         anchors.right: parent.right
0241 
0242         property int selectedFileIndex: -1
0243 
0244         MouseArea {
0245             anchors.fill: parent
0246             onClicked: viewContainer.selectedFileIndex = -1
0247         }
0248 
0249         GridView {
0250             id: creationsList
0251             model: fileNames
0252             flickDeceleration: 1500
0253             width: parent.width - 10
0254             height: parent.height - 10
0255             interactive: true
0256             cellHeight: creationHandler.cellHeight
0257             cellWidth: creationHandler.cellWidth
0258             anchors.top: parent.top
0259             anchors.topMargin: 10
0260             anchors.left: parent.left
0261             anchors.leftMargin: 5
0262             clip: true
0263 
0264             MouseArea {
0265                 anchors.fill: parent
0266                 enabled: !creationHandler.isSaveMode
0267                 onClicked: {
0268                     var itemIndex = creationsList.indexAt(mouseX, mouseY+creationsList.contentY)
0269                     if(itemIndex === -1)
0270                         viewContainer.selectedFileIndex = -1
0271                     else
0272                         viewContainer.selectedFileIndex = itemIndex
0273                 }
0274             }
0275 
0276             delegate: Item {
0277                 height: creationHandler.cellHeight
0278                 width: creationHandler.cellWidth
0279                 readonly property string fileName: fileName.text
0280                 Rectangle {
0281                     anchors.fill: parent
0282                     visible: index === viewContainer.selectedFileIndex
0283                     color: "#E77936"
0284                     opacity: 0.4
0285                     radius: 10
0286                 }
0287 
0288                 Image {
0289                     id: fileIcon
0290                     width: creationHandler.cellWidth
0291                     height: parent.height / 1.5
0292                     anchors.top: parent.top
0293                     anchors.topMargin: 3
0294                     source: "qrc:/gcompris/src/core/resource/file_icon.svg"
0295                 }
0296 
0297                 GCText {
0298                     id: fileName
0299                     anchors.top: fileIcon.bottom
0300                     height: parent.height - fileIcon.height - 15
0301                     width: creationHandler.cellWidth
0302                     font.pointSize: tinySize
0303                     fontSizeMode: Text.Fit
0304                     wrapMode: Text.WordWrap
0305                     horizontalAlignment: Text.AlignHCenter
0306                     // Exclude ".json" while displaying file name
0307                     text: name.slice(0, name.length - 5)
0308                 }
0309             }
0310         }
0311     }
0312 
0313     Row {
0314         id: buttonRow
0315         spacing: 20 * ApplicationInfo.ratio
0316         anchors.horizontalCenter: viewContainer.horizontalCenter
0317         anchors.bottom: keyboardCreation.top
0318         anchors.bottomMargin: 10 * ApplicationInfo.ratio
0319         visible: !creationHandler.isSaveMode
0320         GCButton {
0321             id: loadButton
0322             width: viewContainer.width * 0.5 - 20 * ApplicationInfo.ratio
0323             height: saveButton.height
0324             text: qsTr("Load")
0325             enabled: viewContainer.selectedFileIndex != -1
0326             theme: "highContrast"
0327             onClicked: creationHandler.loadFile()
0328         }
0329 
0330         GCButton {
0331             id: deleteButton
0332             width: loadButton.width
0333             height: saveButton.height
0334             text: qsTr("Delete")
0335             enabled: viewContainer.selectedFileIndex != -1
0336             theme: "highContrast"
0337             onClicked: deleteFile()
0338         }
0339     }
0340 
0341     GCButtonCancel {
0342         id: cancelButton
0343         onClose: {
0344             parent.close()
0345         }
0346     }
0347 
0348     // The scroll buttons
0349     GCButtonScroll {
0350         anchors.right: viewContainer.right
0351         anchors.rightMargin: 5 * ApplicationInfo.ratio
0352         anchors.bottom: viewContainer.bottom
0353         anchors.bottomMargin: 5 * ApplicationInfo.ratio
0354         onUp: creationsList.flick(0, 1000)
0355         onDown: creationsList.flick(0, -1000)
0356         upVisible: creationsList.atYBeginning ? false : true
0357         downVisible: creationsList.atYEnd ? false : true
0358     }
0359 
0360     VirtualKeyboard {
0361         id: keyboardCreation
0362         anchors.bottom: parent.bottom
0363         anchors.horizontalCenter: parent.horizontalCenter
0364         width: parent.width
0365         visible: ApplicationSettings.isVirtualKeyboard && !ApplicationInfo.isMobile && !dialogOpened
0366         onKeypress: {
0367             var textArray = fileNameInput.text.split("");
0368             var cursorPosition = fileNameInput.cursorPosition
0369             if(text == backspace) {
0370                 --cursorPosition;
0371                 textArray.splice(cursorPosition , 1);
0372             }
0373             else {
0374                 textArray.splice(cursorPosition, 0, text);
0375                 ++cursorPosition;
0376             }
0377             fileNameInput.text = textArray.join("");
0378             fileNameInput.cursorPosition = cursorPosition;
0379         }
0380         shiftKey: true
0381         onError: console.log("VirtualKeyboard error: " + msg);
0382         readonly property string newline: "\u21B2"
0383 
0384         function populate() {
0385             layout = [
0386             [
0387                 { label: "0" },
0388                 { label: "1" },
0389                 { label: "2" },
0390                 { label: "3" },
0391                 { label: "4" },
0392                 { label: "5" },
0393                 { label: "6" },
0394                 { label: "7" },
0395                 { label: "8" },
0396                 { label: "9" }
0397             ],
0398             [
0399                 { label: "A" },
0400                 { label: "B" },
0401                 { label: "C" },
0402                 { label: "D" },
0403                 { label: "E" },
0404                 { label: "F" },
0405                 { label: "G" },
0406                 { label: "H" },
0407                 { label: "I" }
0408             ],
0409             [
0410                 { label: "J" },
0411                 { label: "K" },
0412                 { label: "L" },
0413                 { label: "M" },
0414                 { label: "N" },
0415                 { label: "O" },
0416                 { label: "P" },
0417                 { label: "Q" },
0418                 { label: "R" }
0419             ],
0420             [
0421                 { label: "S" },
0422                 { label: "T" },
0423                 { label: "U" },
0424                 { label: "V" },
0425                 { label: "W" },
0426                 { label: "X" },
0427                 { label: "Y" },
0428                 { label: "Z" },
0429                 { label: " " },
0430                 { label: backspace }
0431             ]
0432         ]
0433         }
0434     }
0435 
0436     Keys.onEscapePressed: {
0437         cancelButton.close();
0438     }
0439 
0440     Keys.onTabPressed: {
0441         return;
0442     }
0443 
0444     Keys.onPressed: {
0445         if(event.key === Qt.Key_Left) {
0446             if(viewContainer.selectedFileIndex > 0) {
0447                 viewContainer.selectedFileIndex -= 1;
0448             } else {
0449                 viewContainer.selectedFileIndex = creationsList.count - 1;
0450             }
0451         }
0452         if(event.key === Qt.Key_Right) {
0453             if(viewContainer.selectedFileIndex < creationsList.count - 1) {
0454                 viewContainer.selectedFileIndex += 1;
0455             } else {
0456                 viewContainer.selectedFileIndex = 0;
0457             }
0458         }
0459         if(event.key === Qt.Key_Up || event.key === Qt.Key_Down) {
0460             if(!dialogOpened)
0461                 fileNameInput.forceActiveFocus();
0462         }
0463     }
0464 
0465     Keys.onReleased: {
0466         if(event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {
0467             if(saveButton.visible && !dialogOpened) {
0468                 saveButton.clicked();
0469             } else if(buttonRow.visible && viewContainer.selectedFileIndex != -1){
0470                 loadButton.clicked();
0471             }
0472         }
0473         else if(event.key === Qt.Key_Delete) {
0474             if(buttonRow.visible && viewContainer.selectedFileIndex != -1){
0475                 deleteButton.clicked();
0476             }
0477         }
0478         else if(event.key === Qt.Key_Back) {
0479             cancelButton.close();
0480             event.accepted = true;
0481         }
0482     }
0483 }