File indexing completed on 2024-05-12 15:21:09

0001 /* GCompris - balanceboxeditor.js
0002  *
0003  * SPDX-FileCopyrightText: 2015 Holger Kaelberer <holger.k@elberer.de>
0004  *
0005  * Authors:
0006  *   Holger Kaelberer <holger.k@elberer.de>
0007  *
0008  *   SPDX-License-Identifier: GPL-3.0-or-later
0009  */
0010 
0011 .pragma library
0012 .import QtQuick 2.12 as Quick
0013 .import GCompris 1.0 as GCompris
0014 .import "qrc:/gcompris/src/core/core.js" as Core
0015 
0016 Qt.include("../balancebox_common.js")
0017 
0018 var TOOL_CLEAR = EMPTY
0019 var TOOL_H_WALL = SOUTH
0020 var TOOL_V_WALL = EAST
0021 var TOOL_HOLE = HOLE
0022 var TOOL_CONTACT = CONTACT
0023 var TOOL_GOAL = GOAL
0024 var TOOL_BALL = START
0025 
0026 var levels;
0027 var level;
0028 var currentLevel;
0029 var numberOfLevel;
0030 var levelChanged = false;  // whether current level has unsaved changes
0031 var props;
0032 var currentIsNewLevel;
0033 var targetList = [];
0034 
0035 function initEditor(_props)
0036 {
0037     props = _props;
0038     console.log("init editor");
0039 
0040     currentLevel = 0;
0041     numberOfLevel = 0;
0042     props.lastBallIndex = -1;
0043     props.lastGoalIndex = -1;
0044     levels = [];
0045     if (props.file.exists(props.editor.filename)) {
0046         levels = props.parser.parseFromUrl(props.editor.filename, validateLevels);
0047         if (levels == null) {
0048             console.error("BalanceboxEditor: Error loading levels from "
0049                           + props.editor.filename);
0050             levels = [];  // restart with an empty level-set
0051         }
0052     }
0053     numberOfLevel = levels.length;
0054 
0055     initLevel();
0056 }
0057 
0058 function createEmptyLevel()
0059 {
0060     var map = [];
0061     var num = currentLevel + 1;
0062     for (var row = 0; row < props.rows; row++)
0063         for (var col = 0; col < props.columns; col++) {
0064             if (col === 0)
0065                 map[row] = [];
0066             map[row][col] = 0;
0067         }
0068     return {
0069         level: currentLevel + 1,
0070         map: map,
0071         targets: []
0072     };
0073 }
0074 
0075 function initLevel()
0076 {
0077     props.editor.testBox.loading.start();
0078     if (currentLevel >= numberOfLevel) {
0079         levels.push(createEmptyLevel());
0080         levelChanged = false;
0081         numberOfLevel++;
0082         currentIsNewLevel = true;
0083     } else
0084         currentIsNewLevel = false;
0085 
0086     level = levels[currentLevel];
0087     props.bar.level = currentLevel + 1;
0088     // populate model in the worker-thread:
0089     props.editorWorker.sendMessage({
0090                                        lastBallIndex: props.lastBallIndex,
0091                                        lastGoalIndex: props.lastGoalIndex,
0092                                        lastOrderNum:  props.lastOrderNum,
0093                                        mapModel: props.mapModel,
0094                                        targetList: targetList,
0095                                        level: level
0096                                    });
0097 }
0098 
0099 function dec2hex(i) {
0100    return (i+0x10000).toString(16).substr(-4).toUpperCase();
0101 }
0102 
0103 function modelToLevel()
0104 {
0105     var map = new Array();
0106     var targets = new Array();
0107     targetList.sort(function(a,b) { return a - b;})
0108     for (var i = 0; i < props.mapModel.count; i++) {
0109         var row = Math.floor(i / props.columns);
0110         var col = i % props.columns;
0111         if (col === 0) {
0112             map[row] = new Array();
0113         }
0114 
0115         var obj = props.mapModel.get(i);
0116         var value = obj.value;
0117         value &= ~(0xff00);  // always clear order-number bits
0118         if (obj.value & CONTACT) {
0119             value |= ((targetList.indexOf(parseInt(obj.contactValue)) + 1) << 8);
0120         }
0121         map[row][col] = "0x" + dec2hex(value);
0122     }
0123     var level = {
0124                     level: currentLevel + 1,
0125                     map: map,
0126                     targets: targetList
0127                 }
0128     return level;
0129 }
0130 
0131 function saveModel()
0132 {
0133     var l = modelToLevel();
0134     levels[currentLevel] = l;
0135     // renumber levels before saving:
0136     for(var i = 0; i < levels.length; i++)
0137         levels[i].level = i + 1;
0138 
0139     levelChanged = false;
0140     currentIsNewLevel = false;
0141     return levels
0142 }
0143 
0144 function modifyMap(props, row, col)
0145 {
0146     var modelIndex = row * level.map.length + col;
0147     var obj = props.mapModel.get(modelIndex);
0148     var oldValue = obj.value;
0149     var newValue = oldValue;
0150 
0151     // contact-tool: check for already existing value early
0152     if (props.currentTool === TOOL_CONTACT        // have contact tool and ...
0153             && targetList.indexOf(parseInt(props.contactValue)) !== -1  // already have this contact value ...
0154             && !(obj.value & TOOL_CONTACT                               // which is not set at the same cell
0155                  && obj.contactValue === props.contactValue))
0156     {
0157         console.debug("Avoiding to set duplicate contact value " + props.contactValue
0158                       + " current targets=" + JSON.stringify(targetList));
0159         return;
0160     }
0161 
0162     if (props.currentTool === TOOL_CLEAR) {
0163         newValue = 0;
0164         // remove contact stuff:
0165         if (obj.value & TOOL_CONTACT) {
0166             if (targetList.indexOf(parseInt(obj.contactValue)) !== -1)
0167                 targetList.splice(targetList.indexOf(parseInt(obj.contactValue)), 1);
0168             props.mapModel.setProperty(row * level.map.length + col,
0169                                        "contactValue", "");
0170         }
0171     } else { // all other tools
0172 
0173         // special treatment for mutually exclusive ones:
0174         if (props.currentTool === TOOL_HOLE
0175                 || props.currentTool === TOOL_GOAL
0176                 || props.currentTool === TOOL_CONTACT
0177                 || props.currentTool === TOOL_BALL) {
0178             // helper:
0179             var MUTEX_MASK = (START | GOAL | HOLE | CONTACT) ^ props.currentTool;
0180             newValue &= ~MUTEX_MASK;
0181         }
0182 
0183         // special treatment for singletons:
0184         if (props.currentTool === TOOL_GOAL) {
0185             if ((obj.value & TOOL_GOAL) === 0) {
0186                 // setting a new one
0187                 if (props.lastGoalIndex > -1) {
0188                     // clear last one first:
0189                     props.mapModel.setProperty(props.lastGoalIndex, "value",
0190                                                props.mapModel.get(props.lastGoalIndex).value &
0191                                                (~TOOL_GOAL));
0192                 }
0193                 // now memorize the new one:
0194                 props.lastGoalIndex = modelIndex;
0195             }
0196         } else
0197             if (props.currentTool === TOOL_BALL) {
0198                 if ((obj.value & TOOL_BALL) === 0) {
0199                     // setting a new one
0200                     if (props.lastBallIndex > -1)
0201                         // clear last one first:
0202                         props.mapModel.setProperty(props.lastBallIndex, "value",
0203                                                    props.mapModel.get(props.lastBallIndex).value & (~TOOL_BALL));
0204                     // now memorize the new one:
0205                     props.lastBallIndex = modelIndex;
0206                 }
0207             }
0208 
0209         // special treatment for contacts:
0210         if (props.currentTool === TOOL_CONTACT) {
0211             if (obj.value & TOOL_CONTACT &&                     // have old contact value ...
0212                     obj.contactValue === props.contactValue) {  // ... which is == the new one
0213                 // clear contact
0214                 if (targetList.indexOf(parseInt(obj.contactValue)) !== -1)
0215                     targetList.splice(targetList.indexOf(parseInt(obj.contactValue)), 1);
0216                 props.mapModel.setProperty(row * level.map.length + col,
0217                                            "contactValue", "");
0218                 newValue &= ~(CONTACT);
0219             } else {
0220                 if (obj.value & TOOL_CONTACT) {              // have old contact that is different
0221                     if (targetList.indexOf(parseInt(obj.contactValue)) !== -1)
0222                         targetList.splice(targetList.indexOf(parseInt(obj.contactValue)), 1);
0223                     // no change to newValue
0224                 }
0225                 // -> set new one:
0226                 if (targetList.indexOf(parseInt(props.contactValue)) === -1)
0227                     targetList.push(parseInt(props.contactValue));
0228                 props.mapModel.setProperty(row * level.map.length + col,
0229                                            "contactValue", props.contactValue);
0230                 // the grid is 10*10 so 99 max goals
0231                 var contactValue = Number(props.contactValue);
0232                 if(contactValue < 99) {
0233                     props.contactValue = Number(contactValue + 1).toString();
0234                 }
0235                 newValue |= CONTACT;
0236             }
0237         } else {
0238             // for other than contact-tool: update value by current tool bit:
0239             newValue ^= props.currentTool;
0240         }
0241     }
0242 
0243     if (oldValue !== newValue)
0244         levelChanged = true;
0245     props.mapModel.setProperty(modelIndex, "value", newValue);
0246 }
0247 
0248 var warningVisible = false;
0249 function warnUnsavedChanges(yesFunc, noFunc)
0250 {
0251     if (!warningVisible) {
0252         warningVisible = true;
0253         Core.showMessageDialog(props.editor,
0254                                qsTr("You have unsaved changes!<br/> " +
0255                                     "Do you really want to leave this level and lose your changes?"),
0256                                qsTr("Yes"), function() {
0257                                    warningVisible = false;
0258                                    if (yesFunc !== undefined)
0259                                        yesFunc();
0260                                },
0261                                qsTr("No"), function() {
0262                                    warningVisible = false;
0263                                    if (noFunc !== undefined)
0264                                        noFunc();
0265                                },
0266                                function() {
0267                                    warningVisible = false;
0268                                    if (noFunc !== undefined)
0269                                        noFunc();
0270                                });
0271     }
0272 }
0273 
0274 function nextLevel() {
0275     if(numberOfLevel === currentLevel + 1
0276             && !levelChanged && currentIsNewLevel ) {
0277         console.log("BalanceboxEditor: Current level is new and unchanged, nogo!");
0278         return;
0279     }
0280 
0281     currentLevel++;
0282     levelChanged = false;
0283     initLevel();
0284 }
0285 
0286 function previousLevel() {
0287     if (currentLevel === 0)
0288         return;
0289     currentLevel--;
0290     levelChanged = false;
0291     initLevel();
0292 }