// Algebra.js // Roger Garrett // 12/12/2009 // // This file contains a JavaScript "class" for manipulating algebraic k_equations. // // CONSTRUCTOR for the ALGEBRA "class" // function Algebra() { } var g_isCompletelyLoaded = false; //report("AT STARTUP g_isCompletelyLoaded = "+((g_isCompletelyLoaded==true)?"TRUE":"FALSE")+"\n"); function isCompletelyLoaded() { //report("IN isCompletelyLoaded() g_isCompletelyLoaded = "+((g_isCompletelyLoaded==true)?"TRUE":"FALSE")+"\n"); //reportTabIn(); if (g_isCompletelyLoaded == false) { if (areAllImageFilesLoaded()) { g_isCompletelyLoaded = true; //reportTabOut(); //report("EXITING isCompletelyLoaded() with true because areAllImagesLoaded is true\n"); return true; } //reportTabOut(); //report("EXITING isCompletelyLoaded() with false because areAllImagesLoaded is false\n"); return false; } //reportTabOut(); //report("EXITING isCompletelyLoaded() with true because g_isCompletelyLoaded is already true\n"); return true; } var k_folderStyle = "Styles/"; var k_folderStyleSelected = "Standard/"; var k_folderMathSymbols = "Math Symbols/"; Algebra.setStyle = function(styleName) { // Parameter: // styleName: e.g. "Standard" or "Purple Haze" // The specified styleName MUST exist as a folder on the server within the Styles folder. // By default, the styleName is "Standard". if (!styleName) { reportNullObject("Algebra.setStyle()", "styleName"); return; } // Construct the path and name of a file that SHOULD exist, if the specified styleName is OK. var file; file = k_folderStyle + styleName + "/" + k_folderMathSymbols + "/" + "Digit 0.gif"; // Try to access the file. // NOTE HOw do I do this???? // Assuming that the styleName is correct, set it. k_folderStyleSelected = styleName + "/"; // Now load the related images and sounds. Algebra.loadImages(); Algebra.loadSounds(); // NOTE. I think I need to force a re-load of the page !!! } Algebra.folderMathSymbols = function() { return k_folderStyle + k_folderStyleSelected + k_folderMathSymbols; } Algebra.getFullPathAndNameForSymbolFile = function(fileName) { return Algebra.folderMathSymbols() + fileName + ".gif"; } var k_imageFileNames = [ // These are the names of the image files that are used by Algebra. // Assumes all are .gif types !! "Drag Select Bar", "Operator Bar", "Drop Bar Vertical Non Selected Visible", "Drop Bar Vertical Non Selected Invisible", "Drop Bar Vertical Selected", "Drop Bar Horizontal Non Selected Visible", "Drop Bar Horizontal Non Selected Invisible", "Drop Bar Horizontal Selected", "Digit 0","Digit 1","Digit 2","Digit 3","Digit 4","Digit 5","Digit 6","Digit 7","Digit 8","Digit 9", "Digit 0 Selected","Digit 1 Selected","Digit 2 Selected","Digit 3 Selected","Digit 4 Selected","Digit 5 Selected","Digit 6 Selected","Digit 7 Selected","Digit 8 Selected","Digit 9 Selected", "Digit 0 Small","Digit 1 Small","Digit 2 Small","Digit 3 Small","Digit 4 Small","Digit 5 Small","Digit 6 Small","Digit 7 Small","Digit 8 Small","Digit 9 Small", "Digit 0 Small Selected","Digit 1 Small Selected","Digit 2 Small Selected","Digit 3 Small Selected","Digit 4 Small Selected","Digit 5 Small Selected","Digit 6 Small Selected","Digit 7 Small Selected","Digit 8 Small Selected","Digit 9 Small Selected", "Equals Sign", "Side Separator Line", "Letter A","Letter B","Letter C","Letter D","Letter E","Letter F","Letter G","Letter H","Letter I","Letter J","Letter K","Letter L","Letter M","Letter N","Letter O","Letter P","Letter Q","Letter R","Letter S","Letter T","Letter U","Letter V","Letter W","Letter X","Letter Y","Letter Z", "Letter A Selected","Letter B Selected","Letter C Selected","Letter D Selected","Letter E Selected","Letter F Selected","Letter G Selected","Letter H Selected","Letter I Selected","Letter J Selected","Letter K Selected","Letter L Selected","Letter M Selected","Letter N Selected","Letter O Selected","Letter P Selected","Letter Q Selected","Letter R Selected","Letter S Selected","Letter T Selected","Letter U Selected","Letter V Selected","Letter W Selected","Letter X Selected","Letter Y Selected","Letter Z Selected", "Letter a Small","Letter b Small","Letter c Small","Letter d Small","Letter e Small","Letter f Small","Letter g Small","Letter h Small","Letter i Small","Letter j Small","Letter k Small","Letter l Small","Letter m Small","Letter n Small","Letter o Small","Letter p Small","Letter q Small","Letter r Small","Letter s Small","Letter t Small","Letter u Small","Letter v Small","Letter w Small","Letter x Small","Letter y Small","Letter z Small", "Letter a Small Selected","Letter b Small Selected","Letter c Small Selected","Letter d Small Selected","Letter e Small Selected","Letter f Small Selected","Letter g Small Selected","Letter h Small Selected","Letter i Small Selected","Letter j Small Selected","Letter k Small Selected","Letter l Small Selected","Letter m Small Selected","Letter n Small Selected","Letter o Small Selected","Letter p Small Selected","Letter q Small Selected","Letter r Small Selected","Letter s Small Selected","Letter t Small Selected","Letter u Small Selected","Letter v Small Selected","Letter w Small Selected","Letter x Small Selected","Letter y Small Selected","Letter z Small Selected", // "Operator Divide", "Operator Assignment","Operator Division Line","Operator Minus","Operator Multiply","Operator Multiply Implicit","Operator Multiply Implicit Invisible","Operator Plus", // "Operator Divide Selected", "Operator Assignment Selected","Operator Division Line Selected","Operator Minus Selected","Operator Multiply Selected","Operator Multiply Implicit Selected","Operator Multiply Implicit Selected Invisible","Operator Plus Selected", "Operator Division Line Target","Operator Minus Target","Operator Plus Target", "Parentheses Left","Parentheses Right", "Parentheses Left Selected","Parentheses Right Selected", "Bracket Left","Bracket Right" ]; // This will be an associative array, indexed by the fileName (e.g. "Digit 0"). // Each element of the array consists of an Image object. That Image object will // contain information about the image, including a height and width property. var g_imagesInfo = new Array(); // TEST var g_imagesDiagnostics = ""; Algebra.loadImages = function() { g_imagesDiagnostics = "IN Algebra.loadImages()"; // This function is called from within the BODY of the document // and causes all of the necessary image files to get preloaded. // I THINK that this can also be used whenever the user selects a different Style, which // means that these files come from a different folder. Just call this method again when // setStyle is called. g_imagesInfo.length = 0; var images = k_imageFileNames; for (var index = 0 ; index < images.length ; ++index) { var fileName = k_imageFileNames[index]; var fileFolderAndName = Algebra.getFullPathAndNameForSymbolFile(fileName); g_imagesDiagnostics += "About to load the file : "+ fileFolderAndName + "\n"; // CAN'T USE report here because we're not finished loading. report(fileFolderAndName + "\n"); var newImage = new Image(); // It seems that the following is needed because it somehow gets set to odd, incorrect // values under strange circumstances. //newImage.width = 0; this is readonly //newImage.height = 0; this is readonly newImage.fileFolderAndName = fileFolderAndName; newImage.src = fileFolderAndName + "?"+new Date().getTime(); /// That Date thing supposedly forces a load "?99999"; // + "?654"; // "?1"; //X";//"#"; //"?9876"; // The ?1 was suggested as a way to force it to truly reload it, rather than getting it from cache. // I think that by setting the .src property it causes the file to get loaded in. // WARNING This DOES NOT WORK for Google Chrome or Netsacpe!!!! They do NOT load in the data!!!! g_imagesInfo[fileName] = newImage; if (!g_imagesInfo[fileName]) { //++reportProgrammingError("Algebra.loadImages()","FAILED to store the Image object for " + fileName); } else { var thisImage = g_imagesInfo[fileName]; if (!thisImage) { //++reportProgrammingError("Algebra.loadImages()","FAILED to store the Image object for " + fileName); break; } } } } var g_areAllImageFilesLoaded = false; function areAllImageFilesLoaded() { //report("IN areAllImageFilesLoaded\n"); //reportTabIn(); if (!g_areAllImageFilesLoaded) { for (var fileName in g_imagesInfo) { //report(fileName+"\n"); //alert(fileName+"\n"); var thisImage = g_imagesInfo[fileName]; if (!thisImage) { //alert("areAllImageFilesLoaded() : "+fileName+"\nNo image info for the file."); reportProgrammingError("areAllImageFilesLoaded()", "No image info for "+fileName+".\n"); //reportTabOut(); return false; } else { // if ((thisImage.width == 0) // || (thisImage.height == 0)) if (!thisImage.complete) { //report("NOT LOADED "+fileName+"\n"); adviseUser("areAllImageFilesLoaded: "+fileName+" is NOT LOADED", k_adviseUserImportance_Normal); //reportTabOut(); return false; } //report(fileName+" "+thisImage.width+" X "+thisImage.height+"\n"); } } } //report("ALL LOADED\n"); g_areAllImageFilesLoaded = true; //reportTabOut(); return true; } Algebra.getImageInfo = function(fileName) { if (!fileName) { reportNullObject("Algebra.getImageInfo","fileName"); return; } // Get the related object from the images array. var thisImage = g_imagesInfo[fileName]; if (!thisImage) { alert("Algebra.getImageInfo("+fileName+")\nNo image info for the file. Was not preloaded."); reportProgrammingError("Algebra.getImageInfo("+fileName+")", "No image info for the file. Was not preloaded.\n"); return; } else if(thisImage.width == 0) { reportProgrammingError("Algebra.getImageInfo("+fileName+")",fileName+" width is 0 for "+thisImage.name+"\n"+"complete is "+(thisImage.complete?"TRUE":"FALSE")+"\n"); return; } else if(thisImage.height == 0) { reportProgrammingError("Algebra.getImageInfo("+fileName+")",fileName+" height is 0 for "+thisImage.name+"\n"+"complete is "+(thisImage.complete?"TRUE":"FALSE")+"\n"); return; } //report("IN Algebra.getImageInfo(" + fileName + ") = " + thisImage.width + " X " + thisImage.height + "\n"); return {width:thisImage.width, height:thisImage.height}; } /////////////////////////////////////////////////////////////////////// var k_folderSounds = "Sounds/"; Algebra.folderSounds = function() { return k_folderStyle + k_folderStyleSelected + k_folderSounds; } Algebra.getFullPathAndNameForSoundFile = function(fileName) { return Algebra.folderSounds() + fileName + ".wav"; } var k_soundFileNames = [ // These are the names of the sound files that are used by Algebra. // Assumes all are .wav types !! "Pop" ,"dragObjectToSideSource" ,"dragObjectToSideTarget" ,"dragObjectUppertoLower" ,"dragObjectLowerToUpper" ,"UhOh" // As in, Uh Oh, you can't do that. ]; Algebra.loadSounds = function() { // This function is called from within the BODY of the document. // and causes all of the necessary sound files to get preloaded. // I THINK that this can also be used whenever the user selects a different Style, which // means that these files come from a different folder. Just call this method again when // setStyle is called. // Get the htmlSoundObject, which SHOULD be defined within the BODY of the page and look like this: // var htmlSoundObject = document.getElementById("SOUND_LOADER_ID"); if (!htmlSoundObject) { return; } var sounds = k_soundFileNames; for (var index = 0 ; index < sounds.length ; ++index) { var fileName = sounds[index]; var fileFolderAndName = Algebra.getFullPathAndNameForSoundFile(fileName); // Theoretically this should cause the specified sound file to get loaded in. htmlSoundObject.src = fileName; } } Algebra.soundPlayUhOh = function() { soundPlay("UhOh"); } Algebra.setMouseEventHandlersToDefaults = function(idEquationManipulationBox) { var g_htmlObjectEquationManipulationBox = document.getElementById(idEquationManipulationBox); if (g_htmlObjectEquationManipulationBox != null) { g_htmlObjectEquationManipulationBox.onmouseover = Algebra.onMouseDefault; g_htmlObjectEquationManipulationBox.onmousedown = Algebra.onMouseDefault; g_htmlObjectEquationManipulationBox.onmousemove = Algebra.onMouseDefault; g_htmlObjectEquationManipulationBox.onmouseup = Algebra.onMouseDefault; g_htmlObjectEquationManipulationBox.onmouseout = Algebra.onMouseDefault; } } // Declare the token types. var g_tokenType = 0; //var Algebra_tokenType = "tokenTypes"; var k_Algebra_tokenType_unknown = g_tokenType; var k_Algebra_tokenType_number = ++g_tokenType; var k_Algebra_tokenType_signEquals = ++g_tokenType; var k_Algebra_tokenType_operatorPlus = ++g_tokenType; var k_Algebra_tokenType_operatorMinus = ++g_tokenType; var k_Algebra_tokenType_operatorMultiply = ++g_tokenType; var k_Algebra_tokenType_operatorDivide = ++g_tokenType; var k_Algebra_tokenType_operatorPower = ++g_tokenType; var k_Algebra_tokenType_operatorRoot = ++g_tokenType; var k_Algebra_tokenType_variable = ++g_tokenType; var k_Algebra_tokenType_parenLeft = ++g_tokenType; var k_Algebra_tokenType_parenRight = ++g_tokenType; var g_manipulationBoxRelativeToDocumentLeft = 0; var g_manipulationBoxRelativeToDocumentRight = 0; var g_manipulationBoxRelativeToDocumentTop = 0; var g_manipulationBoxRelativeToDocumentBottom = 0; var g_manipulationBoxHeight = 0; var g_manipulationBoxWidth = 0; var g_HorizontalDropBarPositionTop_ExpressionRight = 0; var g_HorizontalDropBarPositionTop_ExpressionRight = 0; Algebra.getManipulationBoxPositionInfo = function() { //report("IN Algebra.getManipulationBoxPositionInfo()\n"); var pos = getHTMLObjectPositionRelativeToDocument("EquationManipulationBox"); var objectEquationManipulationBox if (pos != null) { g_manipulationBoxRelativeToDocumentLeft = pos.left; g_manipulationBoxRelativeToDocumentRight = pos.left + pos.width - 1; g_manipulationBoxRelativeToDocumentTop = pos.top; g_manipulationBoxRelativeToDocumentBottom = pos.top + pos.height - 1; g_manipulationBoxHeight = pos.height; g_manipulationBoxWidth = pos.width; g_manipulationAreaHorizontalCenter = pos.height / 2; g_manipulationAreaVerticalCenter = pos.width / 2; g_manipulationAreaLeftSideVerticalCenter = pos.width / 4; g_manipulationAreaRightSideVerticalCenter = (pos.width / 4) * 3; //report("g_manipulationBoxRelativeToDocumentLeft = "+ g_manipulationBoxRelativeToDocumentLeft +"\n"); //report("g_manipulationBoxRelativeToDocumentRight = "+ g_manipulationBoxRelativeToDocumentRight +"\n"); //report("g_manipulationBoxRelativeToDocumentTop = "+ g_manipulationBoxRelativeToDocumentTop +"\n"); //report("g_manipulationBoxRelativeToDocumentBottom = "+ g_manipulationBoxRelativeToDocumentBottom +"\n"); } } var g_htmlObjectEquationManipulationBox = null; var g_htmlObjectManipulationArea = null; var g_manipulationAreaHorizontalCenter = 0; var g_manipulationAreaVerticalCenter = 0; var g_manipulationAreaLeftSideVerticalCenter = 0; var g_manipulationAreaRightSideVerticalCenter = 0; Algebra.prototype.putObjectsInEquationManipulationBox = function() { hourglassCursorShow(); //report("IN Algebra.prototype.putObjectsInEquationManipulationBox()\n"); //reportTabIn(); if (!g_hasDoneASuccessfulLexicalAnalyze) { //reportTabOut(); hourglassCursorUnShow(); return; } g_htmlObjectManipulationArea.style.position = "relative"; // Try positioning the equals sign in the center. // // EQUALS SIGN // var htmlObjectsComposite = ""; var imageInfo = Algebra.getImageInfo("Equals Sign"); var equalsSignHeight = imageInfo.height; var equalsSignWidth = imageInfo.width; var left = g_manipulationAreaVerticalCenter - equalsSignWidth / 2; var top = g_manipulationAreaHorizontalCenter - equalsSignHeight / 2; left = g_manipulationAreaVerticalCenter - imageInfo.width / 2; top = g_manipulationAreaHorizontalCenter - imageInfo.height / 2; // We surround everything with a DIV... htmlObjectsComposite = '
'; htmlObjectsComposite += "
" + "\n" + "
" + "\n"; // // UPPER VERTICAL SEPARATOR // // Vertical separator between left and right sides of the equation; imageInfo = Algebra.getImageInfo("Side Separator Line"); left = g_manipulationAreaVerticalCenter - imageInfo.width; top = 0; var height = g_manipulationAreaHorizontalCenter - equalsSignHeight / 2; htmlObjectsComposite += "
" + "\n" + "
" + "\n"; // // LOWER VERTICAL SEPARATOR // // Vertical separator between left and right sides of the equation; top = g_manipulationAreaHorizontalCenter + (equalsSignHeight / 2); htmlObjectsComposite += "
" + "\n" + "
" + "\n"; //////////////// // LEFT SIDE //////////////// // // The parent object needs to be "relative", to indicate that a new local coordinate // system is set up for allcontained objects. //report("BEFORE removeOuterParentheses expressionSideLeft has "+this.equation.expressionSideLeft.objects.length+" objects\n"); Algebra.removeIrrelevantMultiplicationSigns(this.equation.expressionSideLeft); Algebra.removeOuterParentheses(this.equation.expressionSideLeft); //var soleChild = Algebra.getFirstNonDropBarObjectInArray(this.equation.expressionSideLeft.objects); //Algebra.removeIrrelevantGroupings(soleChild); Algebra.removeIrrelevantGroupings(this.equation.expressionSideLeft); Algebra.removeIrrelevantParentheses(this.equation.expressionSideLeft); //report("AFTER removeOuterParentheses expressionSideLeft has "+this.equation.expressionSideLeft.objects.length+" objects\n"); // Force it to recalculate all heights and widths. this.equation.expressionSideLeft.getWidthAndHeight(true); //report("AFTER expressionSideLeft.getWidthAndHeight expressionSideLeft has "+this.equation.expressionSideLeft.objects.length+" objects\n"); //report("\n\nthis.equation.expressionSideLeft HTML = \n\n"+this.equation.expressionSideLeft.toHTML(0,0,k_displayStateNormal)+"\n\n\n"); var heightOfExpression = this.equation.expressionSideLeft.getHeight(false); var expressionTop = (g_manipulationBoxHeight / 2) - (heightOfExpression / 2); var widthOfExpression = this.equation.expressionSideLeft.getWidth(false); var expressionLeft = g_manipulationAreaLeftSideVerticalCenter - (widthOfExpression / 2); var htmlLeftSide = this.equation.expressionSideLeft.toHTML(expressionLeft, expressionTop, k_displayStateNormal); //report("IN Algebra.prototype.putObjectsInEquationManipulationBox\nhtmlLeftSide =\n"+htmlLeftSide+"\n"); // We need to remember the location of the top of the horizontal drop bar that is located beneath the expression, so... g_HorizontalDropBarPositionTop_ExpressionLeft = expressionTop + this.equation.expressionSideLeft.getHeight() - this.equation.expressionSideLeft.horizontalDropBar.getHeight(); //htmlLeftSide.style.position = "relative"; //report("htmlLeftSide = \n"+htmlLeftSide+"\n"); htmlObjectsComposite += htmlLeftSide; ///////////////////////// // RIGHT SIDE ///////////////////////// // // The parent object needs to be "relative", to indicate that a new local coordinate // system is set up for allcontained objects. Algebra.removeIrrelevantMultiplicationSigns(this.equation.expressionSideRight); Algebra.removeOuterParentheses(this.equation.expressionSideRight); //var soleChild = Algebra.getFirstNonDropBarObjectInArray(this.equation.expressionSideRight.objects); //Algebra.removeIrrelevantGroupings(soleChild); Algebra.removeIrrelevantGroupings(this.equation.expressionSideRight); Algebra.removeIrrelevantParentheses(this.equation.expressionSideRight); // Force it to recalculate all heights and widths. this.equation.expressionSideRight.getWidthAndHeight(true); var heightOfExpression = this.equation.expressionSideRight.getHeight(false); var expressionTop = (g_manipulationBoxHeight / 2) - (heightOfExpression / 2); var widthOfExpression = this.equation.expressionSideRight.getWidth(false); var expressionLeft = g_manipulationAreaRightSideVerticalCenter - (widthOfExpression / 2); var htmlRightSide = this.equation.expressionSideRight.toHTML(expressionLeft, expressionTop, k_displayStateNormal); //htmlRightSide.style.position = "relative"; // We need to remember the location of the top of the horizontal drop bar that is located beneath the expression, so... g_HorizontalDropBarPositionTop_ExpressionRight = expressionTop + this.equation.expressionSideRight.getHeight() - this.equation.expressionSideRight.horizontalDropBar.getHeight(); htmlObjectsComposite += htmlRightSide; htmlObjectsComposite += "
"; g_htmlObjectManipulationArea.innerHTML = htmlObjectsComposite; // var o = document.getElementById("EquationManipulationBox"); // report("\n\nEquationManipulationBox = :\n"+EquationManipulationBox.innerHTML+"\n"); var o = document.getElementById("ParentOfEquationManipulationBox"); //report("\n\nParentOfEquationManipulationBox = :\n"+ParentOfEquationManipulationBox.innerHTML+"\n"); //report("\n\nhtmlObjectEquationManipulationBox = \n\n"+g_htmlObjectEquationManipulationBox.innerHTML+"\n\n"); // // SET THE MOUSE EVENT HANDLERS for the MANIPULATION BOX // g_htmlObjectEquationManipulationBox.onmouseover = Algebra.onMouseEnter; g_htmlObjectEquationManipulationBox.onmousedown = Algebra.onMouseDown; g_htmlObjectEquationManipulationBox.onmousemove = Algebra.onMouseMove; g_htmlObjectEquationManipulationBox.onmouseup = Algebra.onMouseUp; g_htmlObjectEquationManipulationBox.onmouseout = Algebra.onMouseLeave; //reportTabOut(); hourglassCursorUnShow(); } var g_hasDoneASuccessfulLexicalAnalyze = false; Algebra.prototype.lexicalAnalyze = function(equationText, idEquationManipulationBox) { // This class method takes the equationText and lexically analyzes it and // converts it into a corresponding structure for.... // // Parameters: // equationText : Text of expression to be analyzed and converted into AlgebraObjects and // corresponding document (HTML) fragments, one for theleft side of the // equation and one for the right side of the equation. // // idEquationManipulationBox: The text string of the id of the HTML page object which contains // the LeftSide and RightSide objects (following). // // This expects that the equationText conforms to the following rules: // // 1. Tokens consist of the following: // = equals sign // + plus sign // - minus sign // * multiplication sign // / division sign // ( left parentheses // ) right parentheses // ^ carat (to indicate exponent) // sqrt square root // 0 - 9 Digits zero through 9 // Integer A sequence of digits, representing an individula integer, e.g. 43 // A - Z Alphabetic characters A through Z // a - z Alphabetic characters a through z // Variable A sequence of alphabetic characters representing a variable, e.g. "A", "AB", "Radius", are valid; "B3", "7A" are not // // 2. All tokens in the equation are separated by single spaces, e.g. A + B = 39 - SQRT ( 2 * X ) / 9 // Of special note: // You cannot use "3A" to mean "three times A", you must use "3 * A" // You cannot speify "+3", you must use "+ 3". // // // // returns the created Algebra.Equation() object. //reportClear(); hourglassCursorShow(); g_hasDoneASuccessfulLexicalAnalyze = false; g_algebraPreviouslySelectedObjectGroupParentId = null; //report("TO Algebra.getManipulationBoxPositionInfo();\n"); Algebra.getManipulationBoxPositionInfo(); //report("BACK FROM Algebra.getManipulationBoxPositionInfo();\n"); g_htmlObjectEquationManipulationBox = document.getElementById(idEquationManipulationBox); g_htmlObjectManipulationArea = document.getElementById("ManipulationArea"); // NEW WARNING IF string shoud not be explicit. Should get passed in to this function. if (!g_htmlObjectManipulationArea) { hourglassCursorUnShow(); alert("Failed to find g_htmlObjectManipulationArea\n"); return null; } // Initially set all mouse event hadlers for the manipulation box to defaults. // We'll later set them to the actual handlers only if the entire lexical analysis goes without errors. if ((g_htmlObjectEquationManipulationBox != null)) { //report("Disable all mouse event handling for the Equation Manipulation Box\n"); // Disable all mouse event handling for the Equation Manipulation Box. Algebra.setMouseEventHandlersToDefaults(idEquationManipulationBox); } else { hourglassCursorUnShow(); reportProgrammingError("Algebra.prototype.lexicalAnalyze(","Failed to locate ManipulationBox or interior cells."); return null; } this.equation = null; if (!equationText || (equationText.length == 0)) { // NOTE: Should do something like put "NO EQUATION ENTERED" in the target boxes. hourglassCursorUnShow(); reportProgrammingError("Algebra.prototype.lexicalAnalyze","No equationText passed in."); return null; } else { // We appear to have some text to translate. // TOKENIZE the equation. var tokenStrings; tokenStrings = Algebra.extractTokenStrings(equationText); if (tokenStrings) { // We have the tokens. // See if they are all valid tokens. var invalidTokenList = ""; var numberOfInvalidTokens = 0; for (var index = 0 ; index < tokenStrings.length ; ++index) { if (!this.isTokenValid(tokenStrings[index])) { ++numberOfInvalidTokens; invalidTokenList += tokenStrings[index] + "\n"; } } if (numberOfInvalidTokens > 0) { // Here is where we report some error. OR cause some error return from this function. // NOTE this should report something tothe USER! hourglassCursorUnShow(); adviseUserExpressionError("This equation contains invalid items.\nDid you forget to put spaces between each item, including around the operators like + and - ?" + invalidTokenList); return null; } else { this.equation = new Algebra.Equation(tokenStrings); if (!this.equation) { // NOTE Report something to the user. hourglassCursorUnShow(); reportProgrammingError("Algebra.prototype.lexicalAnalyze()","Failed in attempt to 'equation = new Algebra.Equation()'"); return null; } else { //report("Back in Algebra.prototype.lexicalAnalyze with aparently valid object-ized equation.\n"); if (!this.equation.expressionSideLeft || !this.equation.expressionSideRight) { hourglassCursorUnShow(); alert("Algebra.prototype.lexicalAnalyze()\n" + "Failed in attempt to generate this.equation.expressionSideLeft\nor this.equation.expressionSideRight.\n"); reportProgrammingError("Algebra.prototype.lexicalAnalyze()", "Failed in attempt to generate this.equation.expressionSideLeft\nor this.equation.expressionSideRight"); return null; } else { //report("We appear to have both this.equation.expressionSideLeft and this.equation.expressionSideRight.\n"); // We seem to have both the this.equation.expressionSideLeft and this.equation.expressionSideRight. //report("BEFORE this.putObjectsInEquationManipulationBox expressionSideLeft has "+g_myAlgebraObject.equation.expressionSideLeft.objects.length+" objects\n"); g_hasDoneASuccessfulLexicalAnalyze = true; this.putObjectsInEquationManipulationBox(); } } } } } hourglassCursorUnShow(); return this.equation; } Algebra.prototype.isTokenValid = function(token) { return (Algebra.getTokenType(token) != k_Algebra_tokenType_unknown); } Algebra.getTokenType = function(token) { // report("IN Algebra.prototype.getTokenType(" + token + ")"); if (Algebra.isAllDigits(token)) { return k_Algebra_tokenType_number; } if (token == "=") { return k_Algebra_tokenType_signEquals; } if (token == "+") { return k_Algebra_tokenType_operatorPlus; } if (token == "-") { return k_Algebra_tokenType_operatorMinus; } if (token == "*") { return k_Algebra_tokenType_operatorMultiply; } if (token == "/") { return k_Algebra_tokenType_operatorDivide; } if (token == "^") { return k_Algebra_tokenType_operatorPower; } if (token == "(") { return k_Algebra_tokenType_parenLeft; } if (token == ")") { return k_Algebra_tokenType_parenRight; } // NOTE Not yet sure how to represent this. //k_Algebra_tokenType_operatorRoot if (Algebra.isAllAlphabetic(token)) { return k_Algebra_tokenType_variable; } return k_Algebra_tokenType_unknown; } Algebra.getTokenTypeName = function(g_tokenType) // g_tokenType is an Integer! { switch (g_tokenType) { case k_Algebra_tokenType_number: return ("Integer"); case k_Algebra_tokenType_signEquals: return ("Equals Sign"); case k_Algebra_tokenType_operatorPlus: return ("Operator Plus"); case k_Algebra_tokenType_operatorMinus: return ("Operator Minus"); case k_Algebra_tokenType_operatorMultiply: return ("Operator Multiply"); case k_Algebra_tokenType_operatorDivide: return ("Operator Divide"); case k_Algebra_tokenType_operatorPower: return ("Operator Power"); case k_Algebra_tokenType_parenLeft: return ("Parentheses Left"); case k_Algebra_tokenType_parenRight: return ("Parenthese Right"); case k_Algebra_tokenType_variable: return ("Variable"); default: return ("!! UNKNOWN !!"); } } Algebra.getTokensNames = function(tokenArray) { // A helper function that takes an array of tokens and generates // a string consiting of the newline-terminated list of token names. var tokenNames = ""; if (tokenArray) { for (var index = 0 ; index < tokenArray.length ; ++index) { tokenNames += "#"+index+" "+tokenArray[index] + "\n"; } } return tokenNames; } Algebra.isDigit = function(digit) { // NOTE This is probably available from some JavaScript standard file. if (digit && digit.length == 1) { var regularExpression = /^([0-9])$/; return (regularExpression.test(digit)); } return false; } Algebra.isAlpha = function(character) { // True if val is a single alphabetic character. var re = /^([a-zA-Z])$/; return (re.test(character)); } Algebra.isLowerCase = function(character) { var re = /^([a-z])$/; return (re.test(character)); } Algebra.isAllDigits = function(text) { // No doubt there is an easier way to do this. if (!text) { return false; } for (var index = 0 ; index < text.length ; ++index) { if (!Algebra.isDigit(text.charAt(index))) { return false; } } return true; } Algebra.isAllAlphabetic = function(text) { if (!text) { return false; } // ??? Make sure text is a character string , accessible with [] text += ""; // NOTE this did NOT help. for (var index = 0 ; index < text.length ; ++index) { if (!Algebra.isAlpha(text.charAt(index))) { return false; } } return true; } Algebra.extractTokenStrings = function(equationText) { var equationTextTokenStrings; // Original that works, sort of. REQUIRES whitespace between each object. //equationTextTokenStrings = equationText.match(/([ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789=\/\-\+\*\(\)]*)/g); //This got me all alphabetic strings but empty array elements for all other characters. // NOTE: The above does it fairly well, although it still puts the whitespace characters into array // New version, as of 2/23/2010 that should NOT require whitespace. // NOTE that this one ALLOWS multi-character variables and as a result REQUIRES a space character in order to indicate an implicit multiplication. //equationTextTokenStrings = equationText.match(/([a-zA-Z]+|[0-9]+|\=|\*|\/|\-|\+|\(|\))/g); // This vesion only allows SINGLE-CHARACTER variables, and as such does NOT require a space in order to indicate implicit multiplication // with the EXCPETION of two adjacent numbers (e.g. 34 78 would reuire a space in order to indicate that it should be a multiplication. equationTextTokenStrings = equationText.match(/([a-zA-Z]|[0-9]+|\=|\*|\/|\-|\+|\(|\))/g); // Let's get rid of the whitespace character; var equationTextTokenStringsWithoutWhitespace = new Array(); if (equationTextTokenStrings) { for (var index = 0 ; index < equationTextTokenStrings.length ; ++index) { if (!Algebra.isAllWhitespace(equationTextTokenStrings[index])) { equationTextTokenStringsWithoutWhitespace.push(equationTextTokenStrings[index]); } } } // TEST Show what we think we have if (equationTextTokenStringsWithoutWhitespace) { var theTokens = equationTextTokenStringsWithoutWhitespace; var reportText = "Original Equation:\n" + equationText + "\n\n"; // Compose a string for our report. for (var index = 0 ; index < theTokens.length ; ++index) { var theToken = theTokens[index]; var g_tokenType = Algebra.getTokenType(theToken); var tokenTypeName = Algebra.getTokenTypeName(g_tokenType); reportText += ( "#" + index + " " + theToken + " = [" + tokenTypeName + "]\n" ); } report(reportText); } else { report("Failed to tokenize " + equationText); } return equationTextTokenStringsWithoutWhitespace; } Algebra.isAllWhitespace = function(text) { if (text) { for (var index = 0 ; index < text.length ; ++index) { if (text[index] != " ") { return false; } } } return true; } //////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////// Algebra.checkForAlreadyInToHTML = function(alreadyInToHTML) { if (alreadyInToHTML == true) { alert("SEVERE ERROR.\n\nApparent infinite loop in calling toHTML for some object.\n\nDO NOT PROCEED\n\nRecord this error and notify Roger\n\nKILL INTERNET EXPLORER"); return true; } return false; } var k_displayStateNormal = 1; var k_displayStateSelected = 2; var k_displayStateTarget = 3; var k_displayStateHidden = 4; // The idea here is that when we lexically analyze the equation text we then create a bunch of // objects that represent the tokens and the composite items ON THE WEB PAGE, and we attach these // objects to the actual HTML objects ?? AND/OR these objects actually contain the HTML code, as // well, for what gets inserted into the web page document. Algebra.onMouseEnterForDropBar = function(evt) { //report("IN Algebra.onMouseEnterForDropBar(evt)\n"); // NOTE that this does NOT get called as an event handler directly // from the Drop Bar Vertical. Instead, since the Manipulation Box has // control of the mouse when the dragging is occurring, this method gets // called ONLY directly from the Manipulation Box's event handler, after // it determines that the object that caused the event is a Drop Bar Vertical instance. // We only handle ths when the user is dragging. if (g_isDragging == false) { //report("IN Algebra.onMouseEnterForDropBar(evt) BUT g_isDragging = false\n"); //adviseUser("IN Algebra.onMouseEnterForDropBar(evt) BUT g_isDragging = false", k_adviseUserImportance_Normal); return; } var evt = evt || window.event; if (!evt) { reportProgrammingError("Algebra.onMouseEnterForDropBar(evt)","FAILED TO FIND EVENT\n"); return false; // Cancel event. } var srcElement = evt.target || evt.srcElement; var object = Algebra.getObjectById(g_myAlgebraObject.equation, srcElement.id); if (!object || (object == null) || (!object.setState)) { reportProgrammingError("Algebra.onMouseEnterForDropBar(evt)","BUT FAILED TO FIND OBJECT: "+srcElement.id+" OR it does not have a setState method\n"); return false; } // Get the visibility state of the object. var visibility = srcElement.style.visibility; var isVisible = (visibility == "visible") || (visibility == "inherit"); if (isVisible == true) { soundPlay("Pop"); object.setState(true); } else { // This is NOT a programming error. It is perfectly valid for some drop bars to be hidden // while the user is dragging objects. In particular, the drop bars associated with the original // position of the obejcts being dragged are set to hidden while the dragging is occurring, precisely // so that they are not recognized as potential drop positions. //report("Algebra.onMouseEnterForDropBar(evt)","But drop bar is neither visible nor inherit!\nIt is "+srcElement.style.visibility+"\n"); } return false; } Algebra.onMouseLeaveForDropBar = function(evt) { // NOTE that this does NOT get called as an event handler directly // from the Drop Bar Vertical. Instead, since the Manipulation Box has // control of the mouse when the dragging is occurring, this method gets // called ONLY directly from the Manipulation Box's event handler, after // it determines that the object that caused the event is a Drop Bar Vertical instance. // We only handle ths when the user is dragging. //report("IN Algebra.onMouseLeaveForDropBar(evt)\n"); // We only handle ths when the user is dragging. // ACtually, no, we don't want to test for this. We want to handle this regardless // of whether we're in dragging mode. That's because it's possible (likely) for the // user to release the mouse button while the mouse is on the Drop Bar Vertical, // which sets the g_isDragging variable to false, and only THEN move the mouse off of // the Drop Bar Vertical. If we exited from this method if it's not in drag mode, // then, in that situation, the Drop Bar Vertical would never get set back to Off. //if (g_isDragging == false) //{ // return; //} var evt = evt || window.event; if (!evt) { return false; // Cancel event. } var srcElement = evt.target || evt.srcElement; var object = Algebra.getObjectById(g_myAlgebraObject.equation, srcElement.id); if (!object || (object == null) || (!object.setState)) { reportProgrammingError("Algebra.onMouseLeaveForDropBar(evt)","FAILED TO FIND OBJECT\n"); return false; } object.setState(false); return false; } var k_dummyParentId = "DummyParentId"; // CONSTRUCTOR for class: DROP BAR VERTICAL // Algebra.DropBarVertical = function(parentObject, parentObjectId) { // This handles a Drop Bar Vertical. // // Parameter: // // parentObjectId this.parentObject = parentObject; this.parentObjectId = parentObjectId; this.iD = "DropBarVertical" + Algebra.getNextId(); this.className = "Drop Bar Vertical"; this.value = "|"; this.hoverMessage = "Drop Bar:\nWhen dragging an item, release the mouse button\nto drop the dragged item here."; this.isClickSelectable = false; this.imageFileNameNonSelectedVisible = "Drop Bar Vertical Non Selected Visible"; this.imageFileNameNonSelectedInvisible = "Drop Bar Vertical Non Selected Invisible"; this.imageFileNameSelected = "Drop Bar Vertical Selected"; this.width = 0; // Use getWidth to access. this.height = 0;// Use getHeight to access. NOTE that this should actually get set by the toHTML // method of th object that is attempting to position it, since that object wants // this Drop Bar Vertical to be the same height as the overall object that it's // working on. this.getWidth = function(forceRecalc) { if (this.width == 0 || (forceRecalc == true)) { // We haven't yet extracted the height and width from the pre-loaded image. var imageInfo = Algebra.getImageInfo(this.imageFileNameSelected); if (!imageInfo) { return; } this.width = imageInfo.width; //this.height = imageInfo.height; Height is specified explicitly prior to calling toHTML. } return this.width; } this.getHeight = function(forceRecalc) { if (this.height == 0 || (forceRecalc == true)) { //reportProgrammingError("Algebra.DropBarVertical","height is zero but should have been specified explicitly prior to calling toHTML."); // We haven't yet extracted the height and width from the pre-loaded image. var imageInfo = Algebra.getImageInfo(this.imageFileNameSelected); if (!imageInfo) { return; } this.width = imageInfo.width; this.height = imageInfo.height; // Height SHOULD HAVE BEEN passed in specified explicitly prior to calling toHTML but wasn't. Default to the image height } return this.height; } this.isStateOn = false; this.setState = function(State) { //report("IN Algebra.DropBarVertical.setState()\n"); // Parameters: // State true = ON or false = OFF // Should ONLY call this AFTER the toHTML() method has been called //and the object was actually inserted into the page. this.isStateOn = State; var image = document.getElementById(this.iD); if (image && image.src) { //report("Found the image\n"); // Looks like we found the image object. if (State == true) { //report("Setting ON\n"); image.src = Algebra.getFullPathAndNameForSymbolFile(this.imageFileNameSelected); } else { //report("Setting OFF\n"); image.src = Algebra.getFullPathAndNameForSymbolFile(g_showDropBars?this.imageFileNameNonSelectedVisible:this.imageFileNameNonSelectedInvisible); } } } this.toString = function() { var theString = reportTab() + "[>DROP BAR VERTICAL>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "imageFileNameOn = " + this.imageFileNameOn + "\n"; theString += reportTab() + "imageFileNameOff = " + this.imageFileNameOff + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; reportTabOut(); theString += reportTab() + "[" + "\n" + "" + "\n"; this.alreadyInToHTML = false; return textHTML; } return; } // CONSTRUCTOR for class: DROP BAR HORIZONTAL // Algebra.DropBarHorizontal = function(parentObject, parentObjectId) { // This handles a Drop Bar Horizontal, which is used only for dropping the denomninator of a fraction. // // Parameter: // // parentObjectId this.parentObject = parentObject; this.parentObjectId = parentObjectId; this.iD = "DropBarHorizontal" + Algebra.getNextId(); this.className = "Drop Bar Horizontal"; this.value = "_"; this.hoverMessage = "Drop Bar:\nWhen dragging an item, release the mouse button\nto drop the dragged item here."; this.isClickSelectable = false; this.imageFileNameNonSelectedVisible = "Drop Bar Horizontal Non Selected Visible"; this.imageFileNameNonSelectedInvisible = "Drop Bar Horizontal Non Selected Invisible"; this.imageFileNameSelected = "Drop Bar Horizontal Selected"; this.width = 0; // Use getWidth to access. NOTE that this should actually get set by the toHTML // method of the object that is attempting to position it, since that object wants // this horizontal Drop Bar to be the same width as the overall object that it's // applied to. this.height = 0;// Use getHeight to access. this.getWidth = function(forceRecalc) { if (this.width == 0 || (forceRecalc == true)) { // We haven't yet extracted the height and width from the pre-loaded image. var imageInfo = Algebra.getImageInfo(this.imageFileNameSelected); if (!imageInfo) { return; } this.height = imageInfo.height; //this.width = imageInfo.width; Width is specified explicitly prior to calling toHTML. } return this.width; } this.getHeight = function(forceRecalc) { if (this.height == 0 || (forceRecalc == true)) { //reportProgrammingError("Algebra.DropBarHorizontal","hight is zero but should have been specified explicitly prior to calling toHTMLr."); // We haven't yet extracted the height and width from the pre-loaded image. var imageInfo = Algebra.getImageInfo(this.imageFileNameSelected); if (!imageInfo) { return 0; } this.width = imageInfo.width;// Width SHOULD HAVE BEEN specified explicitly prior to calling toHTML but wasn't. Default to the image height this.height = imageInfo.height; //report("IN Algebra.DropBarHorizontal.getHeight returning "+this.height+"\n"); } return this.height; } this.isStateOn = false; this.setState = function(State) { //report("IN Algebra.DropBarHorizontal.setState("+(State?"TRUE":"FALSE")+")\n"); //adviseUser("IN Algebra.DropBarHorizontal.setState("+(State?"TRUE":"FALSE")+")\n", k_adviseUserImportance_Normal); // Parameters: // State true = ON or false = OFF // Should ONLY call this AFTER the toHTML() method has been called //and the object was actually inserted into the page. this.isStateOn = State; var image = document.getElementById(this.iD); if (image && image.src) { //report("Found the image\n"); // Looks like we found the image object. if (State == true) { //report("Setting Selected\n"); image.src = Algebra.getFullPathAndNameForSymbolFile(this.imageFileNameSelected); } else { //report("Setting Nonselected\n"); image.src = Algebra.getFullPathAndNameForSymbolFile(g_showDropBars?this.imageFileNameNonSelectedVisible:this.imageFileNameNonSelectedInvisible); } } } this.toString = function() { var theString = reportTab() + "[>DROP BAR HORIZONTAL>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "imageFileNameOn = " + this.imageFileNameOn + "\n"; theString += reportTab() + "imageFileNameOff = " + this.imageFileNameOff + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; reportTabOut(); theString += reportTab() + "[" + "\n" + "" + "\n"; this.alreadyInToHTML = false; return textHTML; } return; } // CONSTRUCTOR for class: SPACE // Algebra.Space = function(parentObject, parentObjectId, width, height) { // This handles a single space character. // // Parameter: // width The desired width of the space character // height The desired height of the space character this.parentObject = parentObject; this.parentObjectId = parentObjectId; this.className = "Space"; this.value = " "; this.hoverMessage = "Space"; this.isClickSelectable = false; this.imageFileName = "Space"; this.height = height; this.width = width; this.isDragable = false; this.getWidth = function(forceRecalc) { if (this.width == 0 || (forceRecalc == true)) { // We haven't yet extracted the height and width from the pre-loaded image. var imageInfo = Algebra.getImageInfo(this.imageFileName); this.width = imageInfo.width; this.height = imageInfo.height; } return this.width; } this.getHeight = function(forceRecalc) { if (this.height == 0 || (forceRecalc == true)) { // We haven't yet extracted the height and width from the pre-loaded image. var imageInfo = Algebra.getImageInfo(this.imageFileName); this.width = imageInfo.width; this.height = imageInfo.height; } return this.height; } this.toString = function() { var theString = reportTab() + "[>SPACE>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "isDraggable " + this.isDragable + "\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; reportTabOut(); theString += reportTab() + "[DIGIT>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "digitValue = " + this.digitValue + "\n"; theString += reportTab() + "imageFileName = " + this.imageFileName + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "isDraggable " + this.isDragable + "\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; reportTabOut(); theString += reportTab() + "[" + "\n" + "" + "\n"; ***/ textHTML = "
" + this.digitCharacter + "
" + "\n"; this.alreadyInToHTML = false; return textHTML; } return; } var k_interDigitSpaceWidth = 1; // CONSTRUCTOR for class: INTEGER // Algebra.Integer = function(parentObject, parentObjectId, digitString) { // This handles a string of digits. // // Parameter: // digitString A string of digits. this.parentObject = parentObject; this.parentObjectId = parentObjectId; this.iD = "Integer_" + digitString + "_" + Algebra.getNextId(); this.className = "Integer"; this.value = digitString; this.hoverMessage = "Number:\nClick to select or de-select.\nClick-n-drag to move."; this.isClickSelectable = true; if (!digitString || (digitString.length < 1)) { reportProgrammingError("Algebra.Integer()","Argument is null or empty."); return; } if (Algebra.isAllDigits(digitString) == false) { reportProgrammingError("Algebra.Integer(" + digitString + ")",digitString + " is not all digits"); return; } // Create the set of digit instances as a property of this integer instance. NOTE ! May need some "SPACE" characters between them!! this.objects = new Array(); for (var index = 0 ; index < digitString.length ; ++index) { var digit = digitString.charAt(index); this.objects.push(new Algebra.Digit(this, this.iD, digit)); } this.digitString = digitString; this.width = 0; this.height = 0; this.getWidthAndHeight = function(forceRecalc) { // Determine the maximum height and total width of the digit objects that comprise this Integer object. var maxHeight = 0; var totalWidth = 0; var numberOfDigits = this.objects.length; for (var index = 0 ; index < numberOfDigits ; ++index) { var thisDigit = this.objects[index]; var thisDigitHeight = thisDigit.getHeight(forceRecalc); if (thisDigitHeight > maxHeight) { maxHeight = thisDigitHeight; } totalWidth += thisDigit.getWidth(forceRecalc); } // Remember the width and height for this Integer object. this.width = totalWidth + (numberOfDigits - 1) * k_interDigitSpaceWidth; // accounts for the interDigitSpacing as well this.height = maxHeight; return {width:this.width, height:this.height}; }; this.getWidth = function(forceRecalc) { if (this.width == 0 || (forceRecalc == true)) { this.getWidthAndHeight(forceRecalc); } return this.width; }; this.getHeight = function(forceRecalc) { if (this.height == 0 || (forceRecalc == true)) { this.getWidthAndHeight(forceRecalc); } return this.height; }; this.isDragable = false; this.setDragable = function(dragable) { // Should ONLY call this AFTER the toHTML() method has been called //and the object was actually inserted into the page. this.isDragable = dragable; } this.currentDisplayState = k_displayStateNormal; this.setDisplayState = function(displayState) { // Parameters: // displayState k_displayStateNormal, k_displayStateSelected, k_displayStateTarget, or k_displayStateHidden // Should ONLY call this AFTER the toHTML() method has been called //and the object was actually inserted into the page. this.currentDisplayState = displayState; //report("this.currentDisplayState now = " + this.currentDisplayState + "\n"); for (var index = 0 ; index < this.objects.length ; ++index) { var object = this.objects[index]; if (object && object.setDisplayState) { object.setDisplayState(displayState); } } } this.toString = function() { var theString = reportTab() + "[>INTEGER>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "digitString = " + this.digitString + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "isDraggable = " + this.isDragable + "\n"; theString += reportTab() + "consists of " + this.objects.length + " digits:\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; reportTabIn(); for (var index = 0 ; index < this.objects.length ; ++index) { theString += this.objects[index].toString(); } reportTabOut(); // to end the list. reportTabOut(); // to end the [INTEGER] theString += reportTab() + "[" + "\n"; // Set the position of each of the Digit objects, so that they are // positioned relative to the digit area of this div object. var xOffsetFromLeftSideOfDiv = 0; var objectsAreaHeight = this.getHeight(false); for (var index = 0 ; index < numberOfDigits ; ++index) { var thisDigit = this.objects[index]; // Calculate the top offset for this digit. This must take into account the height // of the thisDigit object, so that we align all of the digits along the bottom (baseline) // of the div object var yOffsetFromTopSideOfDiv = objectsAreaHeight - thisDigit.getHeight(false); textHTML += thisDigit.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); // Prepare for where the next digit will be positioned, to the right of this one. xOffsetFromLeftSideOfDiv += thisDigit.getWidth(false) + k_interDigitSpaceWidth; } textHTML += "" + "\n"; this.alreadyInToHTML = false; return textHTML; } } function displayStateToText(displayState) { var text = ""; if (displayState == k_displayStateNormal) { text = "NORMAL"; } else if (displayState == k_displayStateSelected) { text = "SELECTED"; } else if (displayState == k_displayStateTarget) { text = "TARGET"; } else { text = "UNKNOWN"; } return(text); } var k_CharacterPointSize_Normal = 20; var k_CharacterPointSize_Small = 15; var k_CharacterColor_Normal = "#000000"; // black var k_CharacterColor_Selected = "#00FF00"; // green // CONSTRUCTOR for class: LETTER // Algebra.Letter = function(parentObject, parentObjectId, letter) { // This handles a single alphabetic character. // // Parameter: // letter: a single alphabetic character. this.parentObject = parentObject; this.parentObjectId = parentObjectId; this.iD = "Letter_" + letter + "_" + Algebra.getNextId(); this.className = "Letter"; this.value = letter; this.hoverMessage = "Variable:\nClick to select or de-select.\nClick-n-drag to move."; this.isClickSelectable = true; if (Algebra.isAlpha(letter) == false) { reportProgrammingError("Algebra.Letter(" + letter + ")", letter + " is not an alphabetic character."); } this.letter = letter; // Create its related imageFileName and save it as a property of this instance. // This should result in something like "Letter A.gif" // NOTE This absolutely requires that we actually do have the necessary files corresponding to all ten digits. // NOTE Probably should allow specification of where the image files reside. We might actually provide a means of // selecting from among some integer of different "styles" for the characters we use, having them reside in separate folders on the server. this.imageFileName = "Letter " + this.letter; if (Algebra.isLowerCase(this.letter) == true) { this.imageFileName += " Small"; } this.imageFileNameSelected = this.imageFileName + " Selected"; //this.imageFileName; this.width = 0; // NOTE Not finished Should Calculate the TOTAL width of all the images, including the space images. this.height = 0; // NOTE Not finished. Should calculate the maximum height of the set of images. this.getWidth = function(forceRecalc) { //!!ROGEROGER return Characters.getSize("Arial", k_CharacterPointSize_Normal, this.letter).width; if (this.width == 0 || (forceRecalc == true)) { var imageInfo = Algebra.getImageInfo(this.imageFileName); this.width = imageInfo.width; this.height = imageInfo.height; } return this.width; } this.getHeight = function(forceRecalc) { //!!ROGEROGER return Characters.getSize("Arial", k_CharacterPointSize_Normal, this.letter).height; if (this.height == 0 || (forceRecalc == true)) { var imageInfo = Algebra.getImageInfo(this.imageFileName); this.width = imageInfo.width; this.height = imageInfo.height; } return this.height; } this.isDragable = false; this.currentDisplayState = k_displayStateNormal; this.setDisplayState = function(displayState) { // Parameters: // displayState k_displayStateNormal, k_displayStateSelected, k_displayStateTarget, or k_displayStateHidden //report("IN Letter.setDisplayState("+displayStateToText(displayState)+")\n"); // Parameters: // displayState k_displayStateNormal, k_displayStateSelected or k_displayStateTarget // Should ONLY call this AFTER the toHTML() method has been called //and the object was actually inserted into the page. this.currentDisplayState = displayState; //!!ROGERROGER /* var image = document.getElementById(this.iD); if (image && image.src) { // Looks like we found the image object. if (displayState == k_displayStateSelected) { image.src = Algebra.getFullPathAndNameForSymbolFile(this.imageFileNameSelected); } else { image.src = Algebra.getFullPathAndNameForSymbolFile(this.imageFileName); } } */ var element = document.getElementById(this.iD); if (displayState == k_displayStateSelected) { element.style.color = k_CharacterColor_Selected; } else { element.style.color = k_CharacterColor_Normal; } } this.toString = function() { var theString = reportTab() + "[>LETTER>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "letter = " + this.letter + "\n"; theString += reportTab() + "imageFileName = " + this.imageFileName + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "isDraggable " + this.isDragable + "\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; reportTabOut(); theString += reportTab() + "[" + "\n" + "" + "\n"; */ textHTML = "
" + this.letter + "
" + "\n"; this.alreadyInToHTML = false; return textHTML; } } var k_interLetterSpaceWidth = 2; // CONSTRUCTOR for class: VARIABLE // Algebra.Variable = function(parentObject, parentObjectId, variable) { // This handles a string of alphabetic characters. // // Parameter: // variable: A string of alphabetic characters. this.parentObject = parentObject; this.parentObjectId = parentObjectId; this.iD = "Variable_" + variable + "_" + Algebra.getNextId(); this.className = "Variable"; this.value = variable; this.hoverMessage = "Variable:\nClick to select or de-select.\nClick-n-drag to move."; this.isClickSelectable = true; if (!variable || (variable.length < 1)) { reportProgrammingError("Algebra.Variable()","Argument is null or empty."); return; } if (Algebra.isAllAlphabetic(variable) == false) { reportProgrammingError("Algebra.Variable(" + variable + ")", variable + " is not all alphabetic characters."); return; } // Create the set of letter instances as a property of this variable instance. NOTE ! May need some "SPACE" characters between them!! this.objects = new Array(); for (var index = 0 ; index < variable.length ; ++index) { var letter = variable.charAt(index); this.objects.push(new Algebra.Letter(this, this.iD, letter)); } this.variable = variable; this.width = 0; this.height = 0; this.getWidthAndHeight = function(forceRecalc) { // Determine the maximum height and total width of the Letter objects that comprise this Variable object. var numberOfLetters = this.objects.length; var maxHeight = 0; var totalWidth = 0; for (var index = 0 ; index < numberOfLetters ; ++index) { var thisLetter = this.objects[index]; var thisLetterHeight = thisLetter.getHeight(forceRecalc); if (thisLetterHeight > maxHeight) { maxHeight = thisLetterHeight; } totalWidth += thisLetter.getWidth(forceRecalc); } // Remember the width and height for this Integer object. this.width = totalWidth + (numberOfLetters - 1) * k_interLetterSpaceWidth; // accounts for the interLetterSpacing as well this.height = maxHeight; return {width:this.width, height:this.height}; }; this.getWidth = function(forceRecalc) { if (this.width == 0 || (forceRecalc == true)) { this.getWidthAndHeight(forceRecalc); } return this.width; }; this.getHeight = function(forceRecalc) { if (this.height == 0 || (forceRecalc == true)) { this.getWidthAndHeight(forceRecalc); } return this.height; }; this.isDragable = false; this.currentDisplayState = k_displayStateNormal; this.setDisplayState = function(displayState) { // Parameters: // displayState k_displayStateNormal, k_displayStateSelected, k_displayStateTarget, or k_displayStateHidden // Should ONLY call this AFTER the toHTML() method has been called //and the object was actually inserted into the page. // Tell all of the contained objects to setSelectState() this.currentDisplayState = displayState; for (var index = 0 ; index < this.objects.length ; ++index) { var object = this.objects[index]; if (object && object.setDisplayState) { object.setDisplayState(displayState); } } } this.toString = function() { var theString = reportTab() + "[>VARIABLE>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "variable = " + this.variable + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "isDraggable " + this.isDragable + "\n"; theString += reportTab() + "consists of " + this.variable.length + " letters:\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; reportTabIn(); for (var index = 0 ; index < this.objects.length ; ++index) { theString += this.objects[index].toString(); } reportTabOut(); // to end the list. reportTabOut(); // to end the [VARIABLE] theString += reportTab() + "[" + "\n"; // Set the position of each of the Letter objects, so that they are // positioned relative to this div object. var xOffsetFromLeftSideOfDiv = 0; var objectsAreaHeight = this.getHeight(false); for (var index = 0 ; index < numberOfLetters ; ++index) { var thisLetter = this.objects[index]; // Calculate the top offset for this letter. This must take into account the height // of the thisLetter object, so that we align all of the letters along the bottom (baseline) // of the div object var yOffsetFromTopSideOfDiv = objectsAreaHeight - thisLetter.getHeight(false); textHTML += thisLetter.toHTML( xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); // Prepare for where the next letter will be positioned, to the right of this one. xOffsetFromLeftSideOfDiv += thisLetter.getWidth(false) + k_interLetterSpaceWidth; } textHTML += "" + "\n"; this.alreadyInToHTML = false; return textHTML; } } // CONSTRUCTOR for class: FUNCTION (e.g. SQRT() ??) // Algebra.Function = function(parentObject, parentObjectId) { // NOT IMPLEMENTED !!!!! this.parentObject = parentObject; this.parentObjectId = parentObjectId; this.className = "Function"; this.hoverMessage = "Function"; this.isClickSelectable = false; this.width = 0; // NOTE Not finished Should Calculate the TOTAL width of all the images, including the space images. this.height = 0; // NOTE Not finished. Should calculate the maximum height of the set of images. this.getWidth = function(forceRecalc) { // NOTE Not finished. return this.width; } this.getHeight = function(forceRecalc) { // NOTE Not finished. return this.height; } this.isDragable = false; this.toString = function() { var theString = reportTab() + "[>FUNCTION>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "isDraggable " + this.isDragable + "\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; reportTabOut(); theString += reportTab() + "[PLUS SIGN>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "imageFileName = " + this.imageFileName + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "isDraggable " + this.isDragable + "\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; reportTabOut(); theString += reportTab() + "[" + "\n" + "" + "\n"; this.alreadyInToHTML = false; return textHTML; } } // CONSTRUCTOR for class: OPERATOR MINUS // Algebra.OperatorMinus = function(parentObject, parentObjectId) { // This represents the minus operator, "-" this.parentObject = parentObject; this.parentObjectId = parentObjectId; this.iD = "OperatorMinus" + Algebra.getNextId(); this.className = "Operator Minus"; this.value = "-"; this.hoverMessage = "Minus Sign (-):\nClick to select or de-select.\nClick-n-drag to move."; this.isClickSelectable = true; this.width = 0; // NOTE Not finished Should Calculate the TOTAL width of all the images, including the space images. this.height = 0; // NOTE Not finished. Should calculate the maximum height of the set of images. this.imageFileName = "Operator Minus"; this.imageFileNameSelected = this.imageFileName + " Selected"; this.imageFileNameTarget = this.imageFileName + " Target"; this.getWidth = function(forceRecalc) { if (this.width == 0 || (forceRecalc == true)) { var imageInfo = Algebra.getImageInfo(this.imageFileName); this.width = imageInfo.width; this.height = imageInfo.height; } return this.width; } this.getHeight = function(forceRecalc) { if (this.height == 0 || (forceRecalc == true)) { var imageInfo = Algebra.getImageInfo(this.imageFileName); this.width = imageInfo.width; this.height = imageInfo.height; } return this.height; } this.isDragable = false; this.currentDisplayState = k_displayStateNormal; this.setDisplayState = function(displayState) { // Parameters: // displayState k_displayStateNormal, k_displayStateSelected or k_displayStateTarget // Should ONLY call this AFTER the toHTML() method has been called //and the object was actually inserted into the page. this.currentDisplayState = displayState; var image = document.getElementById(this.iD); if (image && image.src) { // Looks like we found the image object. if (displayState == k_displayStateSelected) { image.src = Algebra.getFullPathAndNameForSymbolFile(this.imageFileNameSelected); } else if (displayState == k_displayStateNormal) { image.src = Algebra.getFullPathAndNameForSymbolFile(this.imageFileName); } else if (displayState == k_displayStateTarget) { image.src = Algebra.getFullPathAndNameForSymbolFile(this.imageFileNameTarget); } } } this.toString = function() { var theString = reportTab() + "[>MINUS SIGN>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "imageFileName = " + this.imageFileName + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "isDraggable " + this.isDragable + "\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; reportTabOut(); theString += reportTab() + "[" + "\n" + "" + "\n"; this.alreadyInToHTML = false; return textHTML; } } // CONSTRUCTOR for class: OPERATOR MULTIPLY // Algebra.OperatorMultiply = function(parentObject, parentObjectId) { // This represents the multiplication operator, "*" (asterisk) this.parentObject = parentObject; this.parentObjectId = parentObjectId; this.iD = "OperatorMultiply" + Algebra.getNextId(); this.className = "Operator Multiply"; this.value = "*"; this.hoverMessage = "Multiplication Sign (*):\nClick to select or de-select.\nClick-n-drag to move."; this.isClickSelectable = true; this.width = 0; this.height = 0; this.imageFileName = "Operator Multiply"; this.imageFileNameSelected = this.imageFileName + " Selected"; this.getWidth = function(forceRecalc) { if (this.width == 0 || (forceRecalc == true)) { var imageInfo = Algebra.getImageInfo(this.imageFileName); this.width = imageInfo.width; this.height = imageInfo.height; } return this.width; } this.getHeight = function(forceRecalc) { if (this.height == 0 || (forceRecalc == true)) { var imageInfo = Algebra.getImageInfo(this.imageFileName); this.width = imageInfo.width; this.height = imageInfo.height; } return this.height; } this.isDragable = false; this.currentDisplayState = k_displayStateNormal; this.setDisplayState = function(displayState) { // Parameters: // displayState k_displayStateNormal, k_displayStateSelected or k_displayStateTarget // Should ONLY call this AFTER the toHTML() method has been called //and the object was actually inserted into the page. this.currentDisplayState = displayState; var image = document.getElementById(this.iD); if (image && image.src) { // Looks like we found the image object. if (displayState == k_displayStateSelected) { image.src = Algebra.getFullPathAndNameForSymbolFile(this.imageFileNameSelected); } else { image.src = Algebra.getFullPathAndNameForSymbolFile(this.imageFileName); } } } this.toString = function() { var theString = reportTab() + "[>MULTIPLICATION SIGN>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "imageFileName = " + this.imageFileName + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "isDraggable " + this.isDragable + "\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; reportTabOut(); theString += reportTab() + "[" + "\n" + "" + "\n"; this.alreadyInToHTML = false; return textHTML; } } // CONSTRUCTOR for class: OPERATOR MULTIPLY IMPLICIT // Algebra.OperatorMultiplyImplicit = function(parentObject, parentObjectId) { // This represents the implicit multiplication operator. // It consists merely of a blank space. It is used ONLY where we translate two consecutive non-operators into // the left non-operator, this OperatorMultiplyImplicit, and the right non-operator. this.parentObject = parentObject; this.parentObjectId = parentObjectId; this.iD = "OperatorMultiplyImplicit" + Algebra.getNextId(); this.className = "Operator Multiply Implicit"; this.value = "DOT"; this.hoverMessage = "Multiplication:\nClick to select or de-select.\nClick-n-drag to move."; this.isClickSelectable = true; this.width = 0; this.height = 0; this.imageFileName = "Operator Multiply Implicit"; this.imageFileNameInvisible = this.imageFileName + " Invisible"; this.imageFileNameSelected = this.imageFileName + " Selected"; this.imageFileNameSelectedInvisible = this.imageFileNameSelected + " Invisible"; this.getWidth = function(forceRecalc) { //if (this.width == 0 || (forceRecalc == true)) { var imageInfo = Algebra.getImageInfo(g_showImplicitMultiplicationSigns?this.imageFileName:this.imageFileNameInvisible); this.width = imageInfo.width; this.height = imageInfo.height; //report("IN Algebra.OperatorMultiplyImplicit : width = "+this.width+"\n"); } return this.width; } this.getHeight = function(forceRecalc) { //if (this.height == 0 || (forceRecalc == true)) { var imageInfo = Algebra.getImageInfo(g_showImplicitMultiplicationSigns?this.imageFileName:this.imageFileNameInvisible); this.width = imageInfo.width; this.height = imageInfo.height; } return this.height; } this.isDragable = false; this.currentDisplayState = k_displayStateNormal; this.setDisplayState = function(displayState) { // Parameters: // displayState k_displayStateNormal, k_displayStateSelected or k_displayStateTarget // Should ONLY call this AFTER the toHTML() method has been called //and the object was actually inserted into the page. this.currentDisplayState = displayState; var image = document.getElementById(this.iD); if (image && image.src) { // Looks like we found the image object. if (displayState == k_displayStateSelected) { if (g_showImplicitMultiplicationSigns) { image.src = Algebra.getFullPathAndNameForSymbolFile(this.imageFileNameSelected); } else { image.src = Algebra.getFullPathAndNameForSymbolFile(this.imageFileNameSelectedInvisible); } } else { if (g_showImplicitMultiplicationSigns) { image.src = Algebra.getFullPathAndNameForSymbolFile(this.imageFileName); } else { image.src = Algebra.getFullPathAndNameForSymbolFile(this.imageFileNameInvisible); } } } } this.toString = function() { var theString = reportTab() + "[>MULTIPLICATION SIGN IMPLICIT>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "imageFileName = " + this.imageFileName + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "isDraggable " + this.isDragable + "\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; reportTabOut(); theString += reportTab() + "[" + "\n" + "" + "\n"; this.alreadyInToHTML = false; //alert("IN Algebra.OperatorMultiplyImplicit.toHTML with textHTML = \n"+textHTML+"\n"); //report("IN Algebra.OperatorMultiplyImplicit.toHTML with textHTML = \n"+textHTML+"\n"); return textHTML; } } Algebra.includesExplicitMultiply = function(objects) { if (!objects || (objects == null)) { reportNullObject("Algebra.includesExplicitMultiply(objects)","objects"); return false; } var numberOfObjects = objects.length; for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = objects[index]; if (thisObject instanceof Algebra.OperatorMultiply) { return true; } } return false; } // CONSTRUCTOR for class: OPERATOR DIVIDE (NOT the same as a DIVISION LINE) // Algebra.OperatorDivide = function(parentObject, parentObjectId) { // This represents the division operator, "/" // Note that this NEVER actually gets displayed. It represents the division operator while processing // the equation, but when division is displayed, via the GroupFraction() object, it uses OperatorDivisionLine, instead. this.parentObject = parentObject; this.parentObjectId = parentObjectId; this.iD = "Operator Divide" + Algebra.getNextId(); this.className = "Operator Divide"; this.value = "/"; this.hoverMessage = ""; this.isClickSelectable = false; this.width = 0; this.height = 0; this.imageFileName = "Operator Divide"; this.imageFileNameSelected = this.imageFileName + " Selected"; this.getWidth = function(forceRecalc) { reportProgrammingError("Algebra.OperatorDivide().getWidth(forceRecalc)","SHOULD NEVER BE CALLED.\nOperatorDivide is only used for processing the equaiton. never for actual display."); if (this.width == 0 || (forceRecalc == true)) // This is set by the GroupFraction object so that it equals the overall width of the GroupFraction object. { var imageInfo = Algebra.getImageInfo(this.imageFileName); this.width = imageInfo.width; this.height = imageInfo.height; reportProgrammingError("Algebra.OperatorDivide().getWidth(forceRecalc)","Width is zero. Should have been set by corresponding GroupFraction object."); } return this.width; } this.getHeight = function(forceRecalc) { reportProgrammingError("Algebra.OperatorDivide().getHeight(forceRecalc)","SHOULD NEVER BE CALLED.\nOperatorDivide is only used for processing the equaiton. never for actual display."); if (this.height == 0 || (forceRecalc == true)) { var imageInfo = Algebra.getImageInfo(this.imageFileName); //this.width = imageInfo.width; // This is set by the GroupFraction object so that it equals the overall width of the GroupFraction object. this.height = imageInfo.height; } return this.height; } this.isDragable = false; this.currentDisplayState = k_displayStateNormal; this.setDisplayState = function(displayState) { reportProgrammingError("Algebra.OperatorDivide().setDisplayState(forceRecalc)","SHOULD NEVER BE CALLED.\nOperatorDivide is only used for processing the equaiton. never for actual display."); // Parameters: // displayState k_displayStateNormal, k_displayStateSelected or k_displayStateTarget // Should ONLY call this AFTER the toHTML() method has been called //and the object was actually inserted into the page. this.currentDisplayState = displayState; var image = document.getElementById(this.iD); if (image && image.src) { // Looks like we found the image object. if (displayState == k_displayStateSelected) { image.src = Algebra.getFullPathAndNameForSymbolFile(this.imageFileNameSelected); } else { image.src = Algebra.getFullPathAndNameForSymbolFile(this.imageFileName); } } } this.toString = function() { reportProgrammingError("Algebra.OperatorDivide().toString()","SHOULD NEVER BE CALLED.\nOperatorDivide is only used for processing the equaiton. never for actual display."); reportTabIn(); var theString = reportTab() + "[>DIVISION SIGN>]\n"; theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "imageFileName = " + this.imageFileName + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "isDraggable " + this.isDragable + "\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; reportTabOut(); theString += reportTab() + "[" + "\n" + "" + "\n"; this.alreadyInToHTML = false; return textHTML; } } // CONSTRUCTOR for class: OPERATOR DIVISION LINE // Algebra.OperatorDivisionLine = function(parentObject, parentObjectId) { // This represents the division line this.parentObject = parentObject; this.parentObjectId = parentObjectId; this.iD = "OperatorDivisionLine" + Algebra.getNextId(); this.className = "Operator Division"; this.value = "_"; this.hoverMessage = "Fraction Division Line:\nClick to select or de-select the Fraction.\nClick-n-drag to move the Fraction."; this.isClickSelectable = true; this.width = 0; // This is set by the GroupFraction object so that it equals the overall width of the GroupFraction object. this.height = 0; this.imageFileName = "Operator Division Line"; this.imageFileNameSelected = this.imageFileName + " Selected"; this.imageFileNameTarget = this.imageFileName + " Target"; this.getWidth = function(forceRecalc) { if (this.width == 0 || (forceRecalc == true)) // This is set by the GroupFraction object so that it equals the overall width of the GroupFraction object. { // Do NOT do the usual getting of image info. See above comment. //var imageInfo = Algebra.getImageInfo(this.imageFileName); //this.width = imageInfo.width; //this.height = imageInfo.height; reportProgrammingError("Algebra.OperatorDivisionLine().getWidth(forceRecalc)","Width is zero. Should have been set by corresponding GroupFraction object."); } return this.width; } this.getHeight = function(forceRecalc) { if (this.height == 0 || (forceRecalc == true)) { var imageInfo = Algebra.getImageInfo(this.imageFileName); //this.width = imageInfo.width; // This is set by the GroupFraction object so that it equals the overall width of the GroupFraction object. this.height = imageInfo.height; } return this.height; } this.isDragable = false; this.currentDisplayState = k_displayStateNormal; this.setDisplayState = function(displayState) { // Parameters: // displayState k_displayStateNormal, k_displayStateSelected or k_displayStateTarget // Should ONLY call this AFTER the toHTML() method has been called //and the object was actually inserted into the page. this.currentDisplayState = displayState; var image = document.getElementById(this.iD); if (image && image.src) { // Looks like we found the image object. if (displayState == k_displayStateSelected) { image.src = Algebra.getFullPathAndNameForSymbolFile(this.imageFileNameSelected); } else if (displayState == k_displayStateNormal) { image.src = Algebra.getFullPathAndNameForSymbolFile(this.imageFileName); } else if (displayState == k_displayStateTarget) { image.src = Algebra.getFullPathAndNameForSymbolFile(this.imageFileNameTarget); } } } this.toString = function() { var theString = reportTab() + "[>OPERATOR DIVISION LINE>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "imageFileName = " + this.imageFileName + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "isDraggable " + this.isDragable + "\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; reportTabOut(); theString += reportTab() + "[" + "\n" + "" + "\n"; this.alreadyInToHTML = false; return textHTML; } } // CONSTRUCTOR for class: EQUALS SIGN // Algebra.EqualsSign = function(parentObject, parentObjectId) { // This represents the equals sign. //NOTE, I don't think we really need this as part of the expression class. this.parentObject = parentObject; this.parentObjectId = parentObjectId; this.iD = "EqualsSign" + Algebra.getNextId(); this.className = "Equals Sign"; this.value = "="; this.hoverMessage = ""; this.isClickSelectable = false; this.width = 0; // NOTE Not finished Should Calculate the TOTAL width of all the images, including the space images. this.height = 0; // NOTE Not finished. Should calculate the maximum height of the set of images. this.imageFileName = "Equals Sign"; this.imageFileNameSelected = this.imageFileName + " Selected"; this.getWidth = function(forceRecalc) { if (this.width == 0 || (forceRecalc == true)) { var imageInfo = Algebra.getImageInfo(this.imageFileName); this.width = imageInfo.width; this.height = imageInfo.height; } return this.width; } this.getHeight = function(forceRecalc) { if (this.height == 0 || (forceRecalc == true)) { var imageInfo = Algebra.getImageInfo(this.imageFileName); this.width = imageInfo.width; this.height = imageInfo.height; } return this.height; } this.isDragable = false; this.currentDisplayState = k_displayStateNormal; this.setDisplayState = function(displayState) { // Parameters: // displayState k_displayStateNormal, k_displayStateSelected or k_displayStateTarget // Should ONLY call this AFTER the toHTML() method has been called //and the object was actually inserted into the page. this.currentDisplayState = displayState; var image = document.getElementById(this.iD); if (image && image.src) { // Looks like we found the image object. if (displayState == k_displayStateSelected) { image.src = Algebra.getFullPathAndNameForSymbolFile(this.imageFileNameSelected); } else { image.src = Algebra.getFullPathAndNameForSymbolFile(this.imageFileName); } } } this.toString = function() { var theString = reportTab() + "[>EQUALS SIGN>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "imageFileName = " + this.imageFileName + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "isDraggable " + this.isDragable + "\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; reportTabOut(); theString += reportTab() + "[" + "\n" + "" + "\n"; this.alreadyInToHTML = false; return textHTML; } } // CONSTRUCTOR for class: PARENTHESES LEFT // Algebra.ParenthesesLeft = function(parentObject, parentObjectId) { // This represents the left parentheses. this.parentObject = parentObject; this.parentObjectId = parentObjectId; this.iD = "ParenthesesLeft" + Algebra.getNextId(); this.className = "Parentheses Left"; this.value = "("; this.hoverMessage = "Parentheses (:\nClick to select or de-select parenthesized group.\nClick-n-drag to move."; this.isClickSelectable = true; this.width = 0; // NOTE Not finished Should Calculate the TOTAL width of all the images, including the space images. this.height = 0; // NOTE Not finished. Should calculate the maximum height of the set of images. this.imageFileName = "Parentheses Left"; this.imageFileNameSelected = this.imageFileName + " Selected"; this.getWidth = function(forceRecalc) { if (this.width == 0 || (forceRecalc == true)) { var imageInfo = Algebra.getImageInfo(this.imageFileName); this.width = imageInfo.width; this.height = imageInfo.height; } return this.width; } this.getHeight = function(forceRecalc) { if (this.height == 0 || (forceRecalc == true)) { var imageInfo = Algebra.getImageInfo(this.imageFileName); this.width = imageInfo.width; this.height = imageInfo.height; } return this.height; } this.isDragable = false; this.currentDisplayState = k_displayStateNormal; this.setDisplayState = function(displayState) { // Parameters: // displayState k_displayStateNormal, k_displayStateSelected or k_displayStateTarget // Should ONLY call this AFTER the toHTML() method has been called //and the object was actually inserted into the page. this.currentDisplayState = displayState; var image = document.getElementById(this.iD); if (image && image.src) { // Looks like we found the image object. if (displayState == k_displayStateSelected) { image.src = Algebra.getFullPathAndNameForSymbolFile(this.imageFileNameSelected); } else { image.src = Algebra.getFullPathAndNameForSymbolFile(this.imageFileName); } } } this.toString = function() { var theString = reportTab() + "[>PARENTHESES LEFT>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "imageFileName = " + this.imageFileName + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "isDraggable " + this.isDragable + "\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; reportTabOut(); theString += reportTab() + "[" + "\n" + "" + "\n"; this.alreadyInToHTML = false; return textHTML; } } // CONSTRUCTOR for class: PARENTHESES RIGHT // Algebra.ParenthesesRight = function(parentObject, parentObjectId) { // This represents the right parentheses. this.parentObject = parentObject; this.parentObjectId = parentObjectId; this.iD = "ParenthesesRight" + Algebra.getNextId(); this.className = "Parentheses Right"; this.value = ")"; this.hoverMessage = "Parentheses ):\nClick to select or de-select parenthesized group.\nClick-n-drag to move."; this.isClickSelectable = true; this.width = 0; // NOTE Not finished Should Calculate the TOTAL width of all the images, including the space images. this.height = 0; // NOTE Not finished. Should calculate the maximum height of the set of images. this.imageFileName = "Parentheses Right"; this.imageFileNameSelected = this.imageFileName + " Selected"; this.getWidth = function(forceRecalc) { if (this.width == 0 || (forceRecalc == true)) { var imageInfo = Algebra.getImageInfo(this.imageFileName); this.width = imageInfo.width; this.height = imageInfo.height; } return this.width; } this.getHeight = function(forceRecalc) { if (this.height == 0 || (forceRecalc == true)) { var imageInfo = Algebra.getImageInfo(this.imageFileName); this.width = imageInfo.width; this.height = imageInfo.height; } return this.height; } this.isDragable = false; this.currentDisplayState = k_displayStateNormal; this.setDisplayState = function(displayState) { // Parameters: // displayState k_displayStateNormal, k_displayStateSelected or k_displayStateTarget // Should ONLY call this AFTER the toHTML() method has been called //and the object was actually inserted into the page. this.currentDisplayState = displayState; var image = document.getElementById(this.iD); if (image && image.src) { // Looks like we found the image object. if (displayState == k_displayStateSelected) { image.src = Algebra.getFullPathAndNameForSymbolFile(this.imageFileNameSelected); } else { image.src = Algebra.getFullPathAndNameForSymbolFile(this.imageFileName); } } } this.toString = function() { var theString = reportTab() + "[>PARENTHESES RIGHT>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "imageFileName = " + this.imageFileName + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "isDraggable " + this.isDragable + "\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; reportTabOut(); theString += reportTab() + "[" + "\n" + "" + "\n"; this.alreadyInToHTML = false; return textHTML; } } var g_isEnabledGroupingBrackets = false; var g_includeRelatedObjectIdInBracketHoverMessage = false; // CONSTRUCTOR for class: BRACKET LEFT // Algebra.BracketLeft = function(parentObject, parentObjectId, hoverMessage, relatedObjectId) { // This represents the left Bracket. // This is used only for showing groupings of objects and is // only shown when the g_isEnabledGroupingBrackets is true. this.parentObject = parentObject; this.parentObjectId = parentObjectId; this.iD = "BracketLeft" + Algebra.getNextId(); this.className = "Bracket Left"; this.value = "["; //this.title = title; this.hoverMessage = "Bracket: "+hoverMessage; this.relatedObjectId = relatedObjectId; if (g_includeRelatedObjectIdInBracketHoverMessage) { this.hoverMessage += "\n"+this.relatedObjectId; } this.isClickSelectable = false; // if (!title || (title.length == 0)) // { // alert("Algebra.BracketLeft called with empty title."); // } this.width = 0; this.height = 0; this.imageFileName = "Bracket Left"; this.getWidth = function(forceRecalc) { if (this.width == 0 || (forceRecalc == true)) { var imageInfo = Algebra.getImageInfo(this.imageFileName); this.width = imageInfo.width; this.height = imageInfo.height; } return this.width; } this.getHeight = function(forceRecalc) { if (this.height == 0 || (forceRecalc == true)) { var imageInfo = Algebra.getImageInfo(this.imageFileName); this.width = imageInfo.width; this.height = imageInfo.height; } return this.height; } this.isDragable = false; this.toString = function() { var theString = reportTab() + "[>BRACKET LEFT>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "hoverMessage = " + this.hoverMessage+ "\n"; theString += reportTab() + "relatedObjectId= " + this.relatedObjectId+ "\n"; theString += reportTab() + "imageFileName = " + this.imageFileName + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "isDraggable " + this.isDragable + "\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; reportTabOut(); theString += reportTab() + "[" + "\n" + "" + "\n"; this.alreadyInToHTML = false; return textHTML; } } // CONSTRUCTOR for class: BRACKET RIGHT // Algebra.BracketRight = function(parentObject, parentObjectId, hoverMessage, relatedObjectId) { // This represents the right Bracket. // This is used only for showing groupings of objects and is // only shown when the g_isEnabledGroupingBrackets is true. //report("IN Algebra.BracketRight(parentObjectId, hoverMessage, relatedObjectId)\n"); //reportTabIn(); this.parentObject = parentObject; this.parentObjectId = parentObjectId; this.iD = "BracketRight" + Algebra.getNextId(); this.className = "Bracket Right"; this.value = "["; // this.title = title; this.relatedObjectId = relatedObjectId; this.hoverMessage = "Bracket: "+hoverMessage; if (g_includeRelatedObjectIdInBracketHoverMessage) { this.hoverMessage += "\n"+this.relatedObjectId; } // report("this.title = "+this.title+"\n"); //report("this.hoverMessage = "+this.hoverMessage+"\n"); //report("this.relatedObjectId = "+this.relatedObjectId+"\n"); this.isClickSelectable = false; // if (!title || (title.length == 0)) // { // alert("Algebra.BracketRight called with empty title."); // } this.width = 0; this.height = 0; this.imageFileName = "Bracket Right"; this.getWidth = function(forceRecalc) { if (this.width == 0 || (forceRecalc == true)) { var imageInfo = Algebra.getImageInfo(this.imageFileName); this.width = imageInfo.width; this.height = imageInfo.height; } return this.width; } this.getHeight = function(forceRecalc) { if (this.height == 0 || (forceRecalc == true)) { var imageInfo = Algebra.getImageInfo(this.imageFileName); this.width = imageInfo.width; this.height = imageInfo.height; } return this.height; } this.isDragable = false; this.toString = function() { var theString = reportTab() + "[>BRACKET RIGHT>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "hoverMessage= " + this.hoverMessage + "\n"; theString += reportTab() + "imageFileName = " + this.imageFileName + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "isDraggable " + this.isDragable + "\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; reportTabOut(); theString += reportTab() + "[" + "\n" + "" + "\n"; this.alreadyInToHTML = false; //reportTabOut(); return textHTML; } //reportTabOut(); return; } // CONSTRUCTOR for class: GROUP SEQUENCE ADD SUBTRACT (e.g. sequence of adds and subtracts) // Algebra.GroupSequenceAddSubtract = function(parentObject, parentObjectId, theObjects) { //report("IN Algebra.GroupSequenceAddSubtract() with " + theObjects.length + " objects\n"); //reportTabIn(); // This represents a sequence of addition, subtraction, multiplication and division operations. // // Parameter: // theObjects: an array of objects. // Note: theObjects are expected to be only actual algebra objects, NOT drop bars. Drop bars get // added to the set of objects within this constructor. this.parentObject = parentObject; this.parentObjectId = parentObjectId; this.iD = "GroupSequenceAddSubtract" + Algebra.getNextId(); this.className = "Group Sequence Add Subtract"; this.value = ""; this.isClickSelectable = false; this.isGroupTypeObject = true; // Note that non-group type objects do not even have this property. if (!theObjects || (theObjects.length < 1)) { reportNullObject("Algebra.GroupSequenceAddSubtract(parentObjectId, theObjects)","theObjects."); //reportTabOut(); return; } this.setChildrenObjects = function(objects) { //report("IN Algebra.GroupSequenceAddSubtract.setChildrenObjects (objects)\n"); //reportTabIn(); //report("this.id = "+this.iD+"\n"); // NOTE We MUST create a new array and copy over the objects one-by-one // instead of just doing a this.objects = theObjects because the calling function MAY // empty out the original object array referenced by theObjects. // We also insert VerticalDropBars as appropriate. // Remove any leading (extraneous) plus signs. Algebra.removeLeadingPlusSigns(objects); // Remove any trailing (extraneous) plus or minus signs. Algebra.removeTrailingPlusAndMinusSigns(objects); this.objects = new Array(); // Start with one DropBarVertical at the very beginning. this.objects.push(new Algebra.DropBarVertical(this, this.iD)); for (var index = 0 ; index < objects.length ; ++index) { var thisObject = objects[index]; thisObject.parentObject = this; thisObject.parentObjectId = this.iD; this.objects.push(thisObject); if (Algebra.isNonOperator(thisObject)) { // then add another DropBarVertical after the object we just added. this.objects.push(new Algebra.DropBarVertical(this, this.iD)); } } //reportTabOut(); } this.setChildrenObjects(theObjects); // We create the bracket objects EVEN IF we don't happen to be currently showing them. this.bracketLeft = new Algebra.BracketLeft(this.parentObject, this.parentObjectId, "Addition/Subtraction Group",this.iD); this.bracketRight = new Algebra.BracketRight(this.parentObject, this.parentObjectId, "Addition/Subtraction Group",this.iD); this.width = 0; this.height = 0; this.getWidthAndHeight = function(forceRecalc) { // Determine the maximum height and total width of the objects that comprise this group object. var numberOfObjects = this.objects.length; var maxHeight = 0; var totalWidth = 0; for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = this.objects[index]; var thisObjectHeight = thisObject.getHeight(forceRecalc); if (thisObjectHeight > maxHeight) { maxHeight = thisObjectHeight; } totalWidth += thisObject.getWidth(forceRecalc); var isVerticalDropBar = thisObject instanceof Algebra.DropBarVertical; if (!Algebra.isNonOperator(thisObject) && !isVerticalDropBar) { totalWidth += k_interObjectSpaceWidth; } } // Remember the width and height for this object. this.width = totalWidth; // + (numberOfObjects - 1) * k_interObjectSpaceWidth; this.height = maxHeight; if (g_isEnabledGroupingBrackets == true) { this.width += this.bracketLeft.getWidth(forceRecalc) + this.bracketRight.getWidth(forceRecalc); if (this.bracketLeft.getHeight(forceRecalc) > this.height) { this.height = this.bracketLeft.getHeight(forceRecalc); } if (this.bracketRight.getHeight(forceRecalc) > this.height) { this.height = this.bracketRight.getHeight(forceRecalc); } } return {width:this.width, height:this.height}; }; this.getWidth = function(forceRecalc) { if (this.width == 0 || (forceRecalc == true)) { this.getWidthAndHeight(forceRecalc); } return this.width; }; this.getHeight = function(forceRecalc) { if (this.height == 0 || (forceRecalc == true)) { this.getWidthAndHeight(forceRecalc); } return this.height; }; this.isDragable = false; this.currentDisplayState = k_displayStateNormal; this.setDisplayState = function(displayState) { // Parameters: // displayState k_displayStateNormal, k_displayStateSelected, k_displayStateTarget, or k_displayStateHidden // Should ONLY call this AFTER the toHTML() method has been called //and the object was actually inserted into the page. // Tell all of the contained objects to setSelectState() this.currentDisplayState = displayState; for (var index = 0 ; index < this.objects.length ; ++index) { var object = this.objects[index]; if (object && object.setDisplayState) { object.setDisplayState(displayState); } } } this.toString = function() { var theString = reportTab() + "[>GROUP SEQUENCE ADD SUBTRACT>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "isDraggable " + this.isDragable + "\n"; theString += reportTab() + "consists of " + this.objects.length + " objects:\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; reportTabIn(); if (g_isEnabledGroupingBrackets == true) { theString += this.bracketLeft.toString(); } for (var index = 0 ; index < this.objects.length ; ++index) { // NOTE should probably check to assure that it has a toString method!! theString += this.objects[index].toString(); } if (g_isEnabledGroupingBrackets == true) { theString += this.bracketRight.toString(); } reportTabOut(); theString += reportTab() + "[" + "\n"; // Set the position of each of the objects, so that they are // positioned relative to the objects area of this div object. var xOffsetFromLeftSideOfDiv = 0; var objectsAreaHeight = this.getHeight(false); //report("objectsAreaHeight = "+objectsAreaHeight+"\n"); if (g_isEnabledGroupingBrackets == true) { yOffsetFromTopSideOfDiv = (objectsAreaHeight / 2) - (this.bracketLeft.getHeight(false) / 2); textHTML += this.bracketLeft.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); xOffsetFromLeftSideOfDiv += this.bracketLeft.getWidth(false); } for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = this.objects[index]; // If it's a DropBarVertical, adjust its height. var isVerticalDropBar = thisObject instanceof Algebra.DropBarVertical; if (isVerticalDropBar) { thisObject.height = objectsAreaHeight; } // Calculate the top offset for this object. This must take into account the height // of the thisObject object, so that we align all of the objects vertically centered // within the div object var yOffsetFromTopSideOfDiv = (objectsAreaHeight / 2) - (thisObject.getHeight(false) / 2); textHTML += thisObject.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); // Prepare for where the next object will be positioned, to the right of this one. xOffsetFromLeftSideOfDiv += thisObject.getWidth(false);// + k_interDigitSpaceWidth; // NOTE ?? inter OBJECT, perhaps ?? if (!Algebra.isNonOperator(thisObject) && !isVerticalDropBar) { xOffsetFromLeftSideOfDiv += k_interObjectSpaceWidth; } } if (g_isEnabledGroupingBrackets == true) { yOffsetFromTopSideOfDiv = (objectsAreaHeight / 2) - (this.bracketRight.getHeight(false) / 2); textHTML += this.bracketRight.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); xOffsetFromLeftSideOfDiv += this.bracketRight.getWidth(false); } textHTML += "" + "\n"; this.alreadyInToHTML = false; return textHTML; } //reportTabOut(); return; } // CONSTRUCTOR for class: GROUP NUMERATOR // Algebra.GroupNumerator = function(parentObject, parentObjectId, theObject) { //report("IN Algebra.GroupNumerator() CONSTRUCTOR with " + theObject.className+ "\n"); //reportTabIn(); // This represents a sequence of addition, subtraction operations. // // Parameter: // theObject: a single object. // Note: theObjects are expected to be only actual algebra objects, NOT drop bars. Drop bars get // added to the set of objects within this constructor. this.parentObject = parentObject; this.parentObjectId = parentObjectId; this.iD = "GroupNumerator" + Algebra.getNextId(); this.className = "Group Numerator"; this.value = ""; this.isClickSelectable = false; this.isGroupTypeObject = true; // Note that non-group type objects do not even have this property. if (!theObject) { //reportTabOut(); reportNullObject("Algebra.GroupNumerator()","theObject"); return; } this.setChildrenObjects = function(objects) { // NOTE Even though this assumes just ONE object it still comes in as an array, in order to // conform to the other instances of the setChildrenObjects functions. //report("IN Algebra.GroupNumerator.setChildrenObjects()\n"); //reportTabIn(); // There are situations where it is VALID for it to be empty, such as when the entire contents are dragged // someplace else and we haven't yet filled it in with a "1". if (!objects || (objects == null) || !objects.length || (objects.length == 0)) { this.objects.length = 0; // NO! Do not report as error!! reportNullObject("Algebra.GroupNumerator.setChildrenObjects(object)","objects"); //reportTabOut(); return; } if (objects.length != 1) { reportProgrammingError("Algebra.GroupNumerator.setChildrenObjects(object)","Multiple objects passed in, but only one expected."); //reportTabOut(); return; } // We create an array, even though there's only ever ONE object, so that // we can be consistent in how we access objects within groups. this.objects = new Array(); var theObject = objects[0]; if (Algebra.isElementaryObject(theObject)) { // Then we need to enclose it within a GroupSeqeunceAddSubtract. theObject = new Algebra.GroupSequenceAddSubtract(this, this.iD, objects); } theObject.parentObjectId = this.iD; theObject.parentObject = this; this.objects.push(theObject); //report("theObject = "+theObject.className+"\n"); //reportTabOut(); return; } var arrayDummy = new Array(theObject); this.setChildrenObjects(arrayDummy); // We create the bracket objects EVEN IF we don't happen to be currently showing them. this.bracketLeft = new Algebra.BracketLeft(this.parentObject, this.parentObjectId, "Numerator",this.iD); this.bracketRight = new Algebra.BracketRight(this.parentObject, this.parentObjectId, "Numerator",this.iD); this.width = 0; this.height = 0; this.getWidthAndHeight = function(forceRecalc) { //report("IN Algebra.GroupNumerator.getWidthAndHeight()\n"); // Determine the maximum height and total width of the objects that comprise this group object. var numberOfObjects = this.objects.length; var maxHeight = 0; var totalWidth = 0; //report("Going through "+numberOfObjects+" objects to determine the max height\n"); //reportTabIn(); for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = this.objects[index]; //report("Item "+index+" = "+thisObject.className+" : "+thisObject.iD+"\n"); var thisObjectHeight = thisObject.getHeight(forceRecalc); if (thisObjectHeight > maxHeight) { maxHeight = thisObjectHeight; } totalWidth += thisObject.getWidth(forceRecalc); } //reportTabOut(); // Remember the width and height for this object. this.width = totalWidth;// + (numberOfObjects - 1) * k_interObjectSpaceWidth; this.height = maxHeight; if (g_isEnabledGroupingBrackets == true) { this.width += this.bracketLeft.getWidth(forceRecalc) + this.bracketRight.getWidth(forceRecalc); if (this.bracketLeft.getHeight(forceRecalc) > this.height) { this.height = this.bracketLeft.getHeight(forceRecalc); } if (this.bracketRight.getHeight(forceRecalc) > this.height) { this.height = this.bracketRight.getHeight(forceRecalc); } } return {width:this.width, height:this.height}; }; this.getWidth = function(forceRecalc) { if (this.width == 0 || (forceRecalc == true)) { this.getWidthAndHeight(forceRecalc); } return this.width; }; this.getHeight = function(forceRecalc) { if (this.height == 0 || (forceRecalc == true)) { this.getWidthAndHeight(forceRecalc); } return this.height; }; this.isDragable = false; this.currentDisplayState = k_displayStateNormal; this.setDisplayState = function(displayState) { // Parameters: // displayState k_displayStateNormal, k_displayStateSelected, k_displayStateTarget, or k_displayStateHidden // Should ONLY call this AFTER the toHTML() method has been called //and the object was actually inserted into the page. // Tell all of the contained objects to setSelectState() this.currentDisplayState = displayState; for (var index = 0 ; index < this.objects.length ; ++index) { var object = this.objects[index]; if (object && object.setDisplayState) { object.setDisplayState(displayState); } } } this.toString = function() { var theString = reportTab() + "[>GROUP NUMERATOR>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "isDraggable " + this.isDragable + "\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; theString += reportTab() + "consists of " + this.objects.length + " objects:\n"; reportTabIn(); if (g_isEnabledGroupingBrackets == true) { theString += this.bracketLeft.toString(); } for (var index = 0 ; index < this.objects.length ; ++index) { // NOTE should probably check to assure that it has a toString method!! theString += this.objects[index].toString(); } if (g_isEnabledGroupingBrackets == true) { theString += this.bracketRight.toString(); } //reportTabOut(); theString += reportTab() + "[" + "\n"; // Set the position of each of the objects, so that they are // positioned relative to the objects area of this div object. var xOffsetFromLeftSideOfDiv = 0; var objectsAreaHeight = this.getHeight(false); if (g_isEnabledGroupingBrackets == true) { yOffsetFromTopSideOfDiv = (objectsAreaHeight / 2) - (this.bracketLeft.getHeight(false) / 2); textHTML += this.bracketLeft.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); xOffsetFromLeftSideOfDiv += this.bracketLeft.getWidth(false); } for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = this.objects[index]; // If it's a DropBarVertical, adjust its height. if (thisObject instanceof Algebra.DropBarVertical) { thisObject.height = objectsAreaHeight; } // Calculate the top offset for this object. This must take into account the height // of the thisObject object, so that we align all of the objects vertically centered // within the div object var yOffsetFromTopSideOfDiv = (objectsAreaHeight / 2) - (thisObject.getHeight(false) / 2); textHTML += thisObject.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); // Prepare for where the next object will be positioned, to the right of this one. xOffsetFromLeftSideOfDiv += thisObject.getWidth(false); // + k_interDigitSpaceWidth; // NOTE ?? inter OBJECT, perhaps ?? } if (g_isEnabledGroupingBrackets == true) { yOffsetFromTopSideOfDiv = (objectsAreaHeight / 2) - (this.bracketRight.getHeight(false) / 2); textHTML += this.bracketRight.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); xOffsetFromLeftSideOfDiv += this.bracketRight.getWidth(false); } textHTML += "" + "\n"; this.alreadyInToHTML = false; return textHTML; } //reportTabOut(); } var k_fractionPartNone = 0; var k_fractionPartNumerator = 1; var k_fractionPartDenominator = 2; Algebra.determineFractionPart = function(object) { // Given the object this traverses UP the hierarchy and returns one of the following: // k_fractionPartNone // k_fractionPartNumerator // k_fractionPartDenominator // It is only a numerator or denominator object if all of its neighbors are // selected AND it has a numerator or denominator parent (with ONLY intervening // group parentheses [ or groups with just ONE child] between it and the numerator or denominator parent). //report("IN Algebra.determineFractionPart("+object.iD+")\n"); //reportTabIn(); if (!object) { // Do NOT report error, since, when it gets to the very top there // in fact IS no parent object. //reportTabOut(); return k_fractionPartNone; } var parentObjectID = object.parentObjectId; if (!parentObjectID || (parentObjectID == null) || (parentObjectID.length == 0)) { // When it gets to the very top (via recursion) there in fact IS no parent object. // NOTE that we use recursion in this method, rather than looping to get to the parent numerator or denominator. //report("Return from Algebra.determineFractionPart with k_fractionPartNone because hit TOP of hierarchy.\n"); //reportTabOut(); return k_fractionPartNone; } //report("parentObjectID = " + parentObjectID + "\n"); //var parentObject = Algebra.getObjectById(g_myAlgebraObject.equation, parentObjectID); var parentObject = object.parentObject; if (!parentObject || (parentObject == null)) { reportNullObject("Algebra.determineFractionPart", "parentObject"); //report("Return from Algebra.determineFractionPart with k_fractionPartNone because parentObject not found by Algebra.getObjectById.\n"); return k_fractionPartNone; } if (parentObject instanceof Algebra.GroupNumerator) { //report("returning with k_fractionPartNumerator\n"); //reportTabOut(); return k_fractionPartNumerator; } if (parentObject instanceof Algebra.GroupDenominator) { //report("returning with k_fractionPartDenominator\n"); //reportTabOut(); return k_fractionPartDenominator; } // otherwise, recurse up the hierarchy. //reportTabOut(); return Algebra.determineFractionPart(parentObject); } var g_areAllNumeratorChildrenSelected = false; var g_dragObjectNumeratorOrDenominator = null; var g_dragObjectIsInNumeratorOrDenominator = false; var g_dragObjectIsATopObjectInNumeratorOrDenominator = false; var g_isEntireNumeratorOrDenominatorSelected = false; Algebra.generateSourceAndTargetHTMLObjectsForFractionPartNumerator = function(objects, areMultiplyObjects) { if (!objects || (objects == null)) { reportNullObject("Algebra.generateSourceAndTargetHTMLObjectsForFractionPartNumerator(objects, areMultiplyObjects)","objectsForDragItem"); return false; } if (!objects.length || (objects.length == 0)) { reportProgrammingError("Algebra.generateSourceAndTargetHTMLObjectsForFractionPartNumerator(objects, areMultiplyObjects)","No objects to handle."); return false; } g_areAllNumeratorChildrenSelected = Algebra.areAllFractionPartChildrenSelected(objects[0]); g_isEntireNumeratorOrDenominatorSelected = g_areAllNumeratorChildrenSelected; reportTruth("areAllNumeratorChildrenSelected", g_areAllNumeratorChildrenSelected); if ((areMultiplyObjects == false) && (g_areAllNumeratorChildrenSelected == false)) { // Then we're dealing with a sequence of add/subtract objects which are NOT all selected. // That means that they must be handled like a simple sequence selection, rather than as a numerator. // That is, the manipulation of the selected objects can only be done within the sequence. It may not // validly be dragged to the other side of the equation. var generatedOK = Algebra.generateSourceAndTargetHTMLObjectsForAddSubtractObjects(objects); if (generatedOK == false) { reportProgrammingError("Algebra.generateSourceAndTargetHTMLObjectsForFractionPartNumerator(objects, areMultiplyObjects)","Failed to Algebra.generateSourceAndTargetHTMLObjectsForAddSubtractObjects(objects)"); return false; } } // Otherwise we either have (one or more or all) multiply objects OR we have ALL add/subtract objects. // In any of those cases we can validly drag them to the other side and turn them into a denominator. // So we kind of handle it like a simple multiply, putting a division line above it for when it's on the // other side. // Now go through the objects and compose the composite HTML that will // fill the dragItem. var positionLeft = 0; var positionTop; var algebraObject; var heightMaximum = 0; var height; g_dragItemHTMLCodeForSideSource = ""; g_dragItemHTMLCodeForSideTarget = ""; g_dragItemHTMLCodeForSideTargetLower = ""; var numberOfObjects = 0; // // DETERMINE MAX HEIGHT // // Go through the objects and determine their maximum height. //reportClear(); //report("Entering loop with "+objects.length+" objects to calculate max height\n"); for (var index = 0 ; index < objects.length ; ++index) { algebraObject = objects[index]; if (!algebraObject || (algebraObject == null)) { reportNullObject("Algebra.generateSourceAndTargetHTMLObjectsForFractionPartNumerator(objects, areMultiplyObjects)","algebraObject"); return false; } height = algebraObject.getHeight(false); if (height > heightMaximum) { heightMaximum = height; } } ///report("Done getting maximum height = "+heightMaximum+"\n"); var centerlineVerticalForSource = heightMaximum / 2; var divisionLine = new Algebra.OperatorDivisionLine(objects[0].parentObject, objects[0].parentObjectId); var divisionLineHeight = divisionLine.getHeight(); var centerlineVerticalForTarget = centerlineVerticalForSource + divisionLineHeight + k_interDivisionSpaceHeight; var widthTotalForTarget = 0; //report("Composing HTML text for "+objects.length+" objects\n"); for (var index = 0 ; index < objects.length ; ++index) { //report("Looking at object #"+index+"\n"); algebraObject = objects[index]; //report("Is a selected object\n"); ++numberOfObjects; // SOURCE // vertically center... height = algebraObject.getHeight(false); positionTop = centerlineVerticalForSource - (height / 2); //report("About to create HTML code for this object\n"); var objectHTML = algebraObject.toHTML(positionLeft, positionTop, k_displayStateSelected); //report("Back from creating HTML code for this object\n"); g_dragItemHTMLCodeForSideSource += objectHTML; // TARGET height = algebraObject.getHeight(false); positionTop = centerlineVerticalForTarget - (height / 2) var objectHTML = algebraObject.toHTML(positionLeft, positionTop, k_displayStateSelected); g_dragItemHTMLCodeForSideTarget += objectHTML; positionLeft += algebraObject.getWidth(false); widthTotalForTarget += algebraObject.getWidth(false); if (!Algebra.isNonOperator(algebraObject)) { positionLeft += k_interObjectSpaceWidth; widthTotalForTarget += k_interObjectSpaceWidth; } } // Add the division line to the target. divisionLine.width = widthTotalForTarget; divisionLine.height = divisionLineHeight; // Not sure why I need to do this, but it seems to shrink to 1 in height if I don't. g_dragItemHTMLCodeForSideTarget += divisionLine.toHTML(0,0, k_displayStateTarget); //report("divisionLine.toHTML(0,0, k_displayStateTarget); = \n"+divisionLine.toHTML(0,0, k_displayStateTarget)+"\n"); return true; } var g_areAllDenominatorChildrenSelected = false; Algebra.generateSourceAndTargetHTMLObjectsForFractionPartDenominator = function(objects, areMultiplyObjects) { if (!objects || (objects == null)) { reportNullObject("Algebra.generateSourceAndTargetHTMLObjectsForFractionPartDenominator(objects, areMultiplyObjects)","objectsForDragItem"); return false; } if (!objects.length || (objects.length == 0)) { reportProgrammingError("Algebra.generateSourceAndTargetHTMLObjectsForFractionPartDenominator(objects, areMultiplyObjects)","No objects to handle."); return false; } g_areAllDenominatorChildrenSelected = Algebra.areAllFractionPartChildrenSelected(objects[0]); g_isEntireNumeratorOrDenominatorSelected = g_areAllDenominatorChildrenSelected; reportTruth("areAllDenominatorChildrenSelected", g_areAllDenominatorChildrenSelected); if ((areMultiplyObjects == false) && (g_areAllDenominatorChildrenSelected == false)) { // Then we're dealing with a sequence of add/subtract objects which are NOT all selected. // That means that they must be handled like a simple sequence selection, rather than as a denominator. // That is, the manipulation of the selected objects can only be done within the sequence. It may not // validly be dragged to the other side of the equation. return Algebra.generateSourceAndTargetHTMLObjectsForAddSubtractObjects(objects); } // Otherwise we either have (one or more or all) multiply objects OR we have ALL add/subtract objects. // In any of those cases we can validly drag them to the other side and turn them into a denominator. // So we kind of handle it like a simple multiply, putting a division line above it for when it's on the // other side. // Now go through the objects and compose the composite HTML that will // fill the dragItem. var positionLeft = 0; var positionTop; var algebraObject; var heightMaximum = 0; var height; g_dragItemHTMLCodeForSideSource = ""; g_dragItemHTMLCodeForSideTarget = ""; g_dragItemHTMLCodeForSideTargetLower = ""; var numberOfObjects = 0; // // DETERMINE MAX HEIGHT // // Go through the objects and determine their maximum height. //reportClear(); //report("Entering loop with "+objects.length+" objects to calculate max height\n"); for (var index = 0 ; index < objects.length ; ++index) { algebraObject = objects[index]; if (!algebraObject || (algebraObject == null)) { reportNullObject("Algebra.generateSourceAndTargetHTMLObjectsForFractionPartDenominator(objects, areMultiplyObjects)","algebraObject"); return false; } height = algebraObject.getHeight(false); if (height > heightMaximum) { heightMaximum = height; } } ///report("Done getting maximum height = "+heightMaximum+"\n"); var centerlineVerticalForSource = heightMaximum / 2; var divisionLine = new Algebra.OperatorDivisionLine(objects[0].parentObject, objects[0].parentObjectId); var divisionLineHeight = divisionLine.getHeight(); var centerlineVerticalForTarget = centerlineVerticalForSource; // + divisionLineHeight + k_interDivisionSpaceHeight; var widthTotalForTarget = 0; //report("Composing HTML text for "+objects.length+" objects\n"); for (var index = 0 ; index < objects.length ; ++index) { //report("Looking at object #"+index+"\n"); algebraObject = objects[index]; //report("Is a selected object\n"); ++numberOfObjects; // SOURCE // vertically center... height = algebraObject.getHeight(false); positionTop = centerlineVerticalForSource - (height / 2); //report("About to create HTML code for this object\n"); var objectHTML = algebraObject.toHTML(positionLeft, positionTop, k_displayStateSelected); //report("Back from creating HTML code for this object\n"); g_dragItemHTMLCodeForSideSource += objectHTML; // TARGET height = algebraObject.getHeight(false); positionTop = centerlineVerticalForTarget - (height / 2); var objectHTML = algebraObject.toHTML(positionLeft, positionTop, k_displayStateSelected); g_dragItemHTMLCodeForSideTarget += objectHTML; positionLeft += algebraObject.getWidth(false); widthTotalForTarget += algebraObject.getWidth(false); if (!Algebra.isNonOperator(algebraObject)) { positionLeft += k_interObjectSpaceWidth; widthTotalForTarget += k_interObjectSpaceWidth; } } // Add the division line to the target, below the dragged objects. divisionLine.width = widthTotalForTarget; divisionLine.height = divisionLineHeight; // Not sure why I need to do this, but it seems to shrink to 1 in height if I don't. g_dragItemHTMLCodeForSideTarget += divisionLine.toHTML(0,heightMaximum + k_interDivisionSpaceHeight, k_displayStateTarget); //report("divisionLine.toHTML(0,0, k_displayStateTarget); = \n"+divisionLine.toHTML(0,0, k_displayStateTarget)+"\n"); return true; } // CONSTRUCTOR for class: GROUP DENOMINATOR // Algebra.GroupDenominator = function(parentObject, parentObjectId, theObject) { //report("IN Algebra.GroupDenominator() with " + theObject.className+ "\n"); //report("theObject = "+theObject+"\n"); // This represents a sequence of addition, subtraction operations. // // Parameter: // theObject: a SINGLE object. // Note: theObjects are expected to be only actual algebra objects, NOT drop bars. Drop bars get // added to the set of objects within this constructor. this.parentObject = parentObject; this.parentObjectId = parentObjectId; this.iD = "GroupDenominator" + Algebra.getNextId(); this.className = "Group Denominator"; this.value = ""; this.isClickSelectable = false; this.isGroupTypeObject = true; // Note that non-group type objects do not even have this property. if (!theObject) { reportNullObject("Algebra.GroupDenominator()","theObject"); return; } this.setChildrenObjects = function(objects) { // NOTE Even though this assumes just ONE object it still comes in as an array, in order to // conform to the other instances of the setChildrenObjects functions. //report("IN Algebra.GroupDenominator.setChildrenObjects()\n"); if (!objects || (objects == null) || !objects.length || (objects.length == 0)) { // There are situations where it is VALID for it to be empty, such as when the entire contents are dragged // someplace else and we haven't yet filled it in with a "1". this.objects.length = 0; //DO NOT REPORT AS ERROR !! reportNullObject("Algebra.GroupDenominator.setChildrenObjects(object)","objects"); return; } if (objects.length != 1) { reportProgrammingError("Algebra.GroupDenominator.setChildrenObjects(object)","Multiple objects passed in, but only one expected."); return; } // We create an array, even though there's only ever ONE object, so that // we can be consistent in how we access objects within groups. this.objects = new Array(); var theObject = objects[0]; if (Algebra.isElementaryObject(theObject)) { // Then we need to enclose it within a GroupSeqeunceAddSubtract. theObject = new Algebra.GroupSequenceAddSubtract(this, this.iD, objects); } theObject.parentObjectId = this.iD; theObject.parentObject = this; this.objects.push(theObject); //report("theObject = "+theObject.className+"\n"); } var arrayDummy = new Array(theObject); this.setChildrenObjects(arrayDummy); // We create the bracket objects EVEN IF we don't happen to be currently showing them. this.bracketLeft = new Algebra.BracketLeft(this.parentObject, this.parentObjectId, "Denominator",this.iD); this.bracketRight = new Algebra.BracketRight(this.parentObject, this.parentObjectId, "Denominator",this.iD); this.width = 0; this.height = 0; this.getWidthAndHeight = function(forceRecalc) { //report("IN Algebra.GroupDenominator.getWidthAndHeight\n"); // Determine the maximum height and total width of the objects that comprise this group object. var numberOfObjects = this.objects.length; var maxHeight = 0; var totalWidth = 0; for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = this.objects[index]; //report("thisObject = "+thisObject+ " : " + thisObject.className+"\n"); var thisObjectHeight = thisObject.getHeight(forceRecalc); if (thisObjectHeight > maxHeight) { maxHeight = thisObjectHeight; } totalWidth += thisObject.getWidth(forceRecalc); } // Remember the width and height for this object. this.width = totalWidth; // + (numberOfObjects - 1) * k_interObjectSpaceWidth; this.height = maxHeight; if (g_isEnabledGroupingBrackets == true) { this.width += this.bracketLeft.getWidth(forceRecalc) + this.bracketRight.getWidth(forceRecalc); if (this.bracketLeft.getHeight(forceRecalc) > this.height) { this.height = this.bracketLeft.getHeight(forceRecalc); } if (this.bracketRight.getHeight(forceRecalc) > this.height) { this.height = this.bracketRight.getHeight(forceRecalc); } } return {width:this.width, height:this.height}; }; this.getWidth = function(forceRecalc) { if (this.width == 0 || (forceRecalc == true)) { this.getWidthAndHeight(forceRecalc); } return this.width; }; this.getHeight = function(forceRecalc) { if (this.height == 0 || (forceRecalc == true)) { this.getWidthAndHeight(forceRecalc); } return this.height; }; this.isDragable = false; this.currentDisplayState = k_displayStateNormal; this.setDisplayState = function(displayState) { // Parameters: // displayState k_displayStateNormal, k_displayStateSelected, k_displayStateTarget, or k_displayStateHidden // Should ONLY call this AFTER the toHTML() method has been called //and the object was actually inserted into the page. // Tell all of the contained objects to setSelectState() this.currentDisplayState = displayState; for (var index = 0 ; index < this.objects.length ; ++index) { var object = this.objects[index]; if (object && object.setDisplayState) { object.setDisplayState(displayState); } } } this.toString = function() { var theString = reportTab() + "[>GROUP DENOMINATOR>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "isDraggable " + this.isDragable + "\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; theString += reportTab() + "consists of " + this.objects.length + " objects:\n"; reportTabIn(); if (g_isEnabledGroupingBrackets == true) { theString += this.bracketLeft.toString(); } for (var index = 0 ; index < this.objects.length ; ++index) { // NOTE should probably check to assure that it has a toString method!! theString += this.objects[index].toString(); } if (g_isEnabledGroupingBrackets == true) { theString += this.bracketRight.toString(); } reportTabOut(); theString += reportTab() + "[" + "\n"; // Set the position of each of the objects, so that they are // positioned relative to the objects area of this div object. var xOffsetFromLeftSideOfDiv = 0; var objectsAreaHeight = this.getHeight(false); if (g_isEnabledGroupingBrackets == true) { yOffsetFromTopSideOfDiv = (objectsAreaHeight / 2) - (this.bracketLeft.getHeight(false) / 2); textHTML += this.bracketLeft.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); xOffsetFromLeftSideOfDiv += this.bracketLeft.getWidth(false); } for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = this.objects[index]; // Calculate the top offset for this object. This must take into account the height // of the thisObject object, so that we align all of the objects vertically centered // within the div object // If it's a DropBarVertical, adjust its height. if (thisObject instanceof Algebra.DropBarVertical) { thisObject.height = objectsAreaHeight; } var yOffsetFromTopSideOfDiv = (objectsAreaHeight / 2) - (thisObject.getHeight(false) / 2); textHTML += thisObject.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); // Prepare for where the next object will be positioned, to the right of this one. xOffsetFromLeftSideOfDiv += thisObject.getWidth(false); // + k_interDigitSpaceWidth; // NOTE ?? inter OBJECT, perhaps ?? } if (g_isEnabledGroupingBrackets == true) { yOffsetFromTopSideOfDiv = (objectsAreaHeight / 2) - (this.bracketRight.getHeight(false) / 2); textHTML += this.bracketRight.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); xOffsetFromLeftSideOfDiv += this.bracketRight.getWidth(false); } textHTML += "" + "\n"; this.alreadyInToHTML = false; return textHTML; } } // CONSTRUCTOR for class: GROUP EXPRESSION LEFT // Algebra.GroupExpressionLeft = function(parentObject, parentObjectId, theObjects) { //report("IN Algebra.GroupExpressionLeft()\n"); // This represents the left side of the equation. // // Parameter: // theObjects: // Note: theObjects are expected to be only actual algebra objects, NOT drop bars. Drop bars get // added to the set of objects within this constructor. this.parentObject = parentObject; this.parentObjectId = parentObjectId; this.iD = "GroupExpressionLeft" + Algebra.getNextId(); this.className = "Group Expression Left"; this.value = ""; this.isClickSelectable = false; this.isGroupTypeObject = true; // Note that non-group type objects do not even have this property. if (!theObjects) { reportNullObject("Algebra.GroupExpressionLeft()","theObject"); return; } this.setChildrenObjects = function(objects) { //report("IN Algebra.GroupExpressionLeft.setChildrenObjects()\n"); //reportTabIn(); //report("IN Algebra.GroupExpressionLeft.setChildrenObjects " + this.iD + " with " + objects.length + " objects in objects.\n"); this.objects = new Array(); var numberOfAlgebraObjects = Algebra.getNumberOfAlgebraObjectsInArray(objects); //report("numberOfAlgebraObjects = "+numberOfAlgebraObjects+"\n"); if (numberOfAlgebraObjects == 1) { //report("Just ONE algebra object!\n"); var soleAlgebraObject = Algebra.getFirstNonDropBarObjectInArray(objects); //report("soleAlgebraObject = "+soleAlgebraObject.className+"\n"); if (Algebra.isElementaryObject(soleAlgebraObject) || (soleAlgebraObject instanceof Algebra.GroupFraction) ) { //report("The sole object is an elementary object, so create a GroupSequenceAddSubtract for it.\n"); //report("Which is NOT a GroupSequenceAddSubtract, so create one.\n"); // Then we need to construct a GroupSequenceAddSubtract parent for this object. //report("TO Create groupSequence\n"); //reportTabIn(); var parentGroupSequence = new Algebra.GroupSequenceAddSubtract(this, this.iD, objects); //reportTabOut(); //report("BACK FROM Create groupSequence\n"); this.objects.push(parentGroupSequence); } else { // Does not need a GroupSequenceAddSubtract to surround it, so just push it on. soleAlgebraObject.parentObjectId = this.iD; soleAlgebraObject.parentObject = this; this.objects.push(soleAlgebraObject); } } else if (numberOfAlgebraObjects > 1) { reportProgrammingError("Algebra.GroupExpressionLeft.setChildrenObjects","Passed in MULTIPLE objects. Should only be ONE (I think!!)"); //reportTabOut(); return; } this.horizontalDropBar = new Algebra.DropBarHorizontal(this, this.iD); //report("Algebra.GroupExpressionLeft.setChildrenObjects : Adding "+this.horizontalDropBar.iD+"\n"); this.objects.push(this.horizontalDropBar); //reportTabOut(); //report("End up with "+this.objects.length+" objects\n"); } this.setChildrenObjects(theObjects); // We create the bracket objects EVEN IF we don't happen to be currently showing them. this.bracketLeft = new Algebra.BracketLeft(this.parentObject, this.parentObjectId, "Equation Expression Left",this.iD); this.bracketRight = new Algebra.BracketRight(this.parentObject, this.parentObjectId, "Equation Expression Left",this.iD); this.width = 0; this.height = 0; this.getWidthAndHeight = function(forceRecalc) { // NOTE WARNING: Actually for THIS group class and for every group class that only has a single // object we do NOT need to go through the "array" to calculate the height and width. It should // be available as the height and width of the SINGLE object. // Determine the maximum height and total width of the objects that comprise this group object. var numberOfObjects = this.objects.length; var maxHeight = 0; var totalWidth = 0; for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = this.objects[index]; // Note that although the Drop Bar Horizontal for the whole side is included in // the objects array (so that it can be found when searching for it by its ID) we // do NOT process it within this loop. It gets processed AFTER all of the 9other) objects within // the objects array. if (!(thisObject instanceof Algebra.DropBarHorizontal)) { var thisObjectHeight = thisObject.getHeight(forceRecalc); if (thisObjectHeight > maxHeight) { maxHeight = thisObjectHeight; } totalWidth += thisObject.getWidth(forceRecalc); } } // Remember the width and height for this object. this.width = totalWidth; this.height = maxHeight; if (g_isEnabledGroupingBrackets == true) { this.width += this.bracketLeft.getWidth(forceRecalc) + this.bracketRight.getWidth(forceRecalc); if (this.bracketLeft.getHeight(forceRecalc) > this.height) { this.height = this.bracketLeft.getHeight(forceRecalc); } if (this.bracketRight.getHeight(forceRecalc) > this.height) { this.height = this.bracketRight.getHeight(forceRecalc); } } return {width:this.width, height:this.height}; }; this.getWidth = function(forceRecalc) { if (this.width == 0 || (forceRecalc == true)) { this.getWidthAndHeight(forceRecalc); } return this.width; }; this.getHeight = function(forceRecalc) { if (this.height == 0 || (forceRecalc == true)) { this.getWidthAndHeight(forceRecalc); } return this.height; }; this.isDragable = false; this.currentDisplayState = k_displayStateNormal; this.setDisplayState = function(displayState) { // Parameters: // displayState k_displayStateNormal, k_displayStateSelected, k_displayStateTarget, or k_displayStateHidden // Should ONLY call this AFTER the toHTML() method has been called //and the object was actually inserted into the page. // Tell all of the contained objects to setSelectState() this.currentDisplayState = displayState; for (var index = 0 ; index < this.objects.length ; ++index) { var object = this.objects[index]; if (object && object.setDisplayState) { object.setDisplayState(displayState); } } } this.toString = function() { var theString = reportTab() + "[>GROUP EXPRESSION LEFT>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "isDraggable " + this.isDragable + "\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; theString += reportTab() + "consists of " + this.objects.length + " objects:\n"; reportTabIn(); if (g_isEnabledGroupingBrackets == true) { theString += this.bracketLeft.toString(); } for (var index = 0 ; index < this.objects.length ; ++index) { // NOTE should probably check to assure that it has a toString method!! theString += this.objects[index].toString(); } if (g_isEnabledGroupingBrackets == true) { theString += this.bracketRight.toString(); } reportTabOut(); theString += reportTab() + "[" + "\n"; // Set the position of each of the objects, so that they are // positioned relative to the objects area of this div object. var xOffsetFromLeftSideOfDiv = 0; var objectsAreaHeight = this.getHeight(false); if (g_isEnabledGroupingBrackets == true) { yOffsetFromTopSideOfDiv = (objectsAreaHeight / 2) - (this.bracketLeft.getHeight(false) / 2); textHTML += this.bracketLeft.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); xOffsetFromLeftSideOfDiv += this.bracketLeft.getWidth(false); } for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = this.objects[index]; // Note that although the Drop Bar Horizontal for the whole side is included in // the objects array (so that it can be found when searching for it by its ID) we // do NOT process it within this loop. It gets processed AFTER all of the 9other) objects within // the objects array. if (!(thisObject instanceof Algebra.DropBarHorizontal)) { // If it's a DropBarVertical, adjust its height. if (thisObject instanceof Algebra.DropBarVertical) { thisObject.height = objectsAreaHeight; } // Calculate the top offset for this object. This must take into account the height // of the thisObject object, so that we align all of the objects vertically centered // within the div object var yOffsetFromTopSideOfDiv = (objectsAreaHeight / 2) - (thisObject.getHeight(false) / 2); textHTML += thisObject.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); // Prepare for where the next object will be positioned, to the right of this one. xOffsetFromLeftSideOfDiv += thisObject.getWidth(false); // + k_interDigitSpaceWidth; // NOTE ?? inter OBJECT, perhaps ?? } } // Put the horizontal drop bar beneath the entire expression. this.horizontalDropBar.width = this.getWidth(false); textHTML += this.horizontalDropBar.toHTML(0, objectsAreaHeight + k_interDivisionSpaceHeight, displayStateInitial); if (g_isEnabledGroupingBrackets == true) { yOffsetFromTopSideOfDiv = (objectsAreaHeight / 2) - (this.bracketRight.getHeight(false) / 2); textHTML += this.bracketRight.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); xOffsetFromLeftSideOfDiv += this.bracketRight.getWidth(false); } textHTML += "" + "\n"; //report("GroupExpressionLeft HTML:\n"+textHTML+"\n"); this.alreadyInToHTML = false; //report("IN Algebra.GroupExpressionLeft HTML = \n"+textHTML+"\n"); return textHTML; } } // CONSTRUCTOR for class: GROUP EXPRESSION RIGHT // Algebra.GroupExpressionRight = function(parentObject, parentObjectId, theObjects) { //report("IN Algebra.GroupExpressionRight()\n"); // This represents the right side of the equation. // // Parameter: // theObjects: // Note: theObjects are expected to be only actual algebra objects, NOT drop bars. Drop bars get // added to the set of objects within this constructor. this.parentObject = parentObject; this.parentObjectId = parentObjectId; this.iD = "GroupExpressionRight" + Algebra.getNextId(); this.className = "Group Expression Right"; this.value = ""; this.isClickSelectable = false; this.isGroupTypeObject = true; // Note that non-group type objects do not even have this property. if (!theObjects) { reportNullObject("Algebra.GroupExpressionRight()","theObject"); return; } this.setChildrenObjects = function(objects) { //report("IN Algebra.GroupExpressionRight.setChildrenObjects()\n"); //reportTabIn(); //report("IN Algebra.GroupExpressionRight.setChildrenObjects " + this.iD + " with " + objects.length + " objects in objects.\n"); this.objects = new Array(); var numberOfAlgebraObjects = Algebra.getNumberOfAlgebraObjectsInArray(objects); //report("numberOfAlgebraObjects = "+numberOfAlgebraObjects+"\n"); if (numberOfAlgebraObjects == 1) { //report("Just ONE algebra object!\n"); var soleAlgebraObject = Algebra.getFirstNonDropBarObjectInArray(objects); //report("soleAlgebraObject = "+soleAlgebraObject.className+"\n"); if (Algebra.isElementaryObject(soleAlgebraObject) || (soleAlgebraObject instanceof Algebra.GroupFraction) ) { //report("The sole object is an elementary object, so create a GroupSequenceAddSubtract for it.\n"); var parentGroupSequence = new Algebra.GroupSequenceAddSubtract(this, this.iD, objects); this.objects.push(parentGroupSequence); } else { // Does not need a GroupSequenceAddSubtract to surround it, so just push it on. soleAlgebraObject.parentObjectId = this.iD; soleAlgebraObject.parentObject = this; this.objects.push(soleAlgebraObject); } } else if (numberOfAlgebraObjects > 1) { //reportProgrammingError("Algebra.GroupExpressionRight.setChildrenObjects","Passed in MULTIPLE objects. Should only be ONE (I think!!)"); //Algebra.showObjectArray("objects passed in to Algebra.GroupExpressionRight.setChildrenObjects", objects); //reportTabOut(); return; } this.horizontalDropBar = new Algebra.DropBarHorizontal(this, this.iD); //report("IN Algebra.GroupExpressionRight : Adding "+this.horizontalDropBar.iD+"\n"); this.objects.push(this.horizontalDropBar); //reportTabOut(); //report("End up with "+this.objects.length+" objects\n"); } this.setChildrenObjects(theObjects); this.horizontalDropBar = new Algebra.DropBarHorizontal(this, this.iD); this.objects.push(this.horizontalDropBar); // We create the bracket objects EVEN IF we don't happen to be currently showing them. this.bracketLeft = new Algebra.BracketLeft(this.parentObject, this.parentObjectId, "Equation Expression Right",this.iD); this.bracketRight = new Algebra.BracketRight(this.parentObject, this.parentObjectId, "Equation Expression Right",this.iD); this.width = 0; this.height = 0; this.getWidthAndHeight = function(forceRecalc) { // NOTE WARNING: Actually for THIS group class and for every group class that only has a single // object we do NOT need to go through the "array" to calculate the height and width. It should // be available as the height and width of the SINGLE object. // Determine the maximum height and total width of the objects that comprise this group object. var numberOfObjects = this.objects.length; var maxHeight = 0; var totalWidth = 0; for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = this.objects[index]; // Note that although the Drop Bar Horizontal for the whole side is included in // the objects array (so that it can be found when searching for it by its ID) we // do NOT process it within this loop. It gets processed AFTER all of the 9other) objects within // the objects array. if (!(thisObject instanceof Algebra.DropBarHorizontal)) { var thisObjectHeight = thisObject.getHeight(forceRecalc); if (thisObjectHeight > maxHeight) { maxHeight = thisObjectHeight; } totalWidth += thisObject.getWidth(forceRecalc); } } // Remember the width and height for this object. this.width = totalWidth; this.height = maxHeight; if (g_isEnabledGroupingBrackets == true) { this.width += this.bracketLeft.getWidth(forceRecalc) + this.bracketRight.getWidth(forceRecalc); if (this.bracketLeft.getHeight(forceRecalc) > this.height) { this.height = this.bracketLeft.getHeight(forceRecalc); } if (this.bracketRight.getHeight(forceRecalc) > this.height) { this.height = this.bracketRight.getHeight(forceRecalc); } } return {width:this.width, height:this.height}; }; this.getWidth = function(forceRecalc) { if (this.width == 0 || (forceRecalc == true)) { this.getWidthAndHeight(forceRecalc); } return this.width; }; this.getHeight = function(forceRecalc) { if (this.height == 0 || (forceRecalc == true)) { this.getWidthAndHeight(forceRecalc); } return this.height; }; this.isDragable = false; this.currentDisplayState = k_displayStateNormal; this.setDisplayState = function(displayState) { // Parameters: // displayState k_displayStateNormal, k_displayStateSelected, k_displayStateTarget, or k_displayStateHidden // Should ONLY call this AFTER the toHTML() method has been called //and the object was actually inserted into the page. // Tell all of the contained objects to setSelectState() this.currentDisplayState = displayState; for (var index = 0 ; index < this.objects.length ; ++index) { var object = this.objects[index]; if (object && object.setDisplayState) { object.setDisplayState(displayState); } } } this.toString = function() { var theString = reportTab() + "[>GROUP EXPRESSION RIGHT>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "isDraggable " + this.isDragable + "\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; theString += reportTab() + "consists of " + this.objects.length + " objects:\n"; reportTabIn(); if (g_isEnabledGroupingBrackets == true) { theString += this.bracketLeft.toString(); } for (var index = 0 ; index < this.objects.length ; ++index) { // NOTE should probably check to assure that it has a toString method!! theString += this.objects[index].toString(); } if (g_isEnabledGroupingBrackets == true) { theString += this.bracketRight.toString(); } reportTabOut(); theString += reportTab() + "[" + "\n"; // Set the position of each of the objects, so that they are // positioned relative to the objects area of this div object. var xOffsetFromLeftSideOfDiv = 0; var objectsAreaHeight = this.getHeight(false); if (g_isEnabledGroupingBrackets == true) { yOffsetFromTopSideOfDiv = (objectsAreaHeight / 2) - (this.bracketLeft.getHeight(false) / 2); textHTML += this.bracketLeft.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); xOffsetFromLeftSideOfDiv += this.bracketLeft.getWidth(false); } for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = this.objects[index]; // Note that although the Drop Bar Horizontal for the whole side is included in // the objects array (so that it can be found when searching for it by its ID) we // do NOT process it within this loop. It gets processed AFTER all of the 9other) objects within // the objects array. if (!(thisObject instanceof Algebra.DropBarHorizontal)) { // If it's a DropBarVertical, adjust its height. if (thisObject instanceof Algebra.DropBarVertical) { thisObject.height = objectsAreaHeight; } // Calculate the top offset for this object. This must take into account the height // of the thisObject object, so that we align all of the objects vertically centered // within the div object var yOffsetFromTopSideOfDiv = (objectsAreaHeight / 2) - (thisObject.getHeight(false) / 2); textHTML += thisObject.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); // Prepare for where the next object will be positioned, to the right of this one. xOffsetFromLeftSideOfDiv += thisObject.getWidth(false); // + k_interDigitSpaceWidth; // NOTE ?? inter OBJECT, perhaps ?? } } // Put the horizontal drop bar beneath the entire expression. this.horizontalDropBar.width = this.getWidth(false); textHTML += this.horizontalDropBar.toHTML(0, objectsAreaHeight + k_interDivisionSpaceHeight, displayStateInitial); if (g_isEnabledGroupingBrackets == true) { yOffsetFromTopSideOfDiv = (objectsAreaHeight / 2) - (this.bracketRight.getHeight(false) / 2); textHTML += this.bracketRight.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); xOffsetFromLeftSideOfDiv += this.bracketRight.getWidth(false); } textHTML += "" + "\n"; //report("GroupExpressionRight HTML:\n"+textHTML+"\n"); this.alreadyInToHTML = false; return textHTML; } } // CONSTRUCTOR for class: GROUP PARENTHESES // Algebra.GroupParentheses = function(parentObject, parentObjectId, theObjects) { // This represents a parenthesized set of objects, much like the GroupSequenceAddSubtract class but this can enclose // ANY set of objects and is implicitly surrounded by parentheses. // // Parameter: // theObjects: an array of objects, but WITHOUT the enclosing left and right parentheses objects. // Note: theObjects are expected to be only actual algebra objects, NOT drop bars. Drop bars get // added to the set of objects within this constructor. report("IN Algebra.GroupParentheses(parentObject, parentObjectId, theObjects)\n"); reportTabIn(); this.parentObject = parentObject; this.parentObjectId = parentObjectId; this.iD = "GroupParentheses" + Algebra.getNextId(); this.className = "Group Parentheses"; this.value = ""; this.isClickSelectable = false; this.isGroupTypeObject = true; // Note that non-group type objects do not even have this property. if (!theObjects || (theObjects.length < 1)) { reportProgrammingError("Algebra.GroupParentheses()","Argument is null or empty."); reportTabOut(); return null; } this.leftParentheses = new Algebra.ParenthesesLeft(this, this.iD); this.setChildrenObjects = function(objects) { report("IN Algebra.GroupParentheses.setChildrenObjects(objects)\n"); reportTabIn(); // NOTE We MUST create a new array and copy over the objects one-by-one // instead of just doing a this.objects = objects because the calling function MAY // empty out the original object array referenced by objects. this.objects = new Array(); report("IN Algebra.GroupParentheses.setChildrenObjects(objects) " + this.iD + " with " + objects.length + " objects in objects.\n"); for (var index = 0 ; index < objects.length ; ++index) { var thisObject = objects[index]; thisObject.parentObjectId = this.iD; thisObject.parentObject = this; this.objects.push(thisObject); } //report("IN Algebra.GroupParentheses " + this.iD + " with " + this.objects.length + " objects in this.objects AFTER setting their parentIds.\n"); reportTabOut(); } this.setChildrenObjects(theObjects); this.rightParentheses = new Algebra.ParenthesesRight(this, this.iD); // WARNING NOTE: Parentheses might actually have to GROW to accomodate larger sub expressions!!! // Same will be true for the radical sign. // We create the bracket objects EVEN IF we don't happen to be currently showing them. this.bracketLeft = new Algebra.BracketLeft(this.parentObject, this.parentObjectId, "Parenthesized Group",this.iD); this.bracketRight = new Algebra.BracketRight(this.parentObject, this.parentObjectId, "Parenthesized Group",this.iD); this.width = 0; this.height = 0; this.getWidthAndHeight = function(forceRecalc) { //report("IN Algebra.GroupParentheses.getWidthAndHeight " + this.iD + " with " + this.objects.length + " objects in this.objects\n"); // Determine the maximum height and total width of the objects that comprise this group object. var numberOfObjects = this.objects.length; var maxHeight = 0; var totalWidth = 0; for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = this.objects[index]; var thisObjectHeight = thisObject.getHeight(forceRecalc); if (thisObjectHeight > maxHeight) { maxHeight = thisObjectHeight; } totalWidth += thisObject.getWidth(forceRecalc); } if (this.leftParentheses.getHeight(forceRecalc) > maxHeight) { maxHeight = this.leftParentheses.getHeight(forceRecalc); } if (this.rightParentheses.getHeight(forceRecalc) > maxHeight) { maxHeight = this.rightParentheses.getHeight(forceRecalc); } // Remember the width and height for this Object. this.width = totalWidth //+ (numberOfObjects - 1) * k_interObjectSpaceWidth + this.leftParentheses.getWidth(forceRecalc) + this.rightParentheses.getWidth(forceRecalc) //+ (2 * k_interObjectSpaceWidth) ; this.height = maxHeight; if (g_isEnabledGroupingBrackets == true) { this.width += this.bracketLeft.getWidth(forceRecalc) + this.bracketRight.getWidth(forceRecalc); if (this.bracketLeft.getHeight(forceRecalc) > this.height) { this.height = this.bracketLeft.getHeight(forceRecalc); } if (this.bracketRight.getHeight(forceRecalc) > this.height) { this.height = this.bracketRight.getHeight(forceRecalc); } } return {width:this.width, height:this.height}; }; this.getWidth = function(forceRecalc) { //report("IN Algebra.GroupParentheses.getWidth " + this.iD + " with " + this.objects.length + " objects in this.objects\n"); if (this.width == 0 || (forceRecalc == true)) { this.getWidthAndHeight(forceRecalc); } return this.width; } this.getHeight = function(forceRecalc) { //report("IN Algebra.GroupParentheses.getHeight " + this.iD + " with " + this.objects.length + " objects in this.objects\n"); if (this.height == 0 || (forceRecalc == true)) { this.getWidthAndHeight(forceRecalc); } return this.height; } this.isDragable = false; this.currentDisplayState = k_displayStateNormal; this.setDisplayState = function(displayState) { // Parameters: // displayState k_displayStateNormal, k_displayStateSelected, k_displayStateTarget, or k_displayStateHidden //report("IN Algebra.GroupParentheses.setDisplayState " + this.iD + " with " + this.objects.length + " objects in this.objects\n"); // Should ONLY call this AFTER the toHTML() method has been called //and the object was actually inserted into the page. // Tell all of the contained objects to setSelectState() this.currentDisplayState = displayState; this.rightParentheses.setDisplayState(displayState); this.leftParentheses.setDisplayState(displayState); for (var index = 0 ; index < this.objects.length ; ++index) { var object = this.objects[index]; if (object && object.setDisplayState) { object.setDisplayState(displayState); } } } this.toString = function() { //report("IN Algebra.GroupParentheses.toString " + this.iD + " with " + this.objects.length + " objects in this.objects\n"); var theString = reportTab() + "[>GROUP PARENTHESES>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "isDraggable " + this.isDragable + "\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; theString += this.leftParentheses.toString(); theString += reportTab() + "consists of " + this.objects.length + " objects:\n"; reportTabIn(); if (g_isEnabledGroupingBrackets == true) { theString += this.bracketLeft.toString(); } for (var index = 0 ; index < this.objects.length ; ++index) { // NOTE should probably check to assure that it has a toString method!! theString += this.objects[index].toString(); } if (g_isEnabledGroupingBrackets == true) { theString += this.bracketRight.toString(); } reportTabOut(); theString += this.rightParentheses.toString(); reportTabOut(); theString += reportTab() + "[" + "\n"; var xOffsetFromLeftSideOfDiv = 0; if (g_isEnabledGroupingBrackets == true) { yOffsetFromTopSideOfDiv = (objectsAreaHeight / 2) - (this.bracketLeft.getHeight(false) / 2); textHTML += this.bracketLeft.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); xOffsetFromLeftSideOfDiv += this.bracketLeft.getWidth(false); } var yOffsetFromTopSideOfDiv = (objectsAreaHeight / 2) - (this.leftParentheses.getHeight(false) / 2); // LEFT PARENTHESES NOTE WARNING Not adjusting the height. textHTML += this.leftParentheses.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); xOffsetFromLeftSideOfDiv += this.leftParentheses.getWidth(false); // Set the position of each of the contained objects, so that they are // positioned relative to this div object. var numberOfObjects = this.objects.length; for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = this.objects[index]; // If it's a DropBarVertical, adjust its height. if (thisObject instanceof Algebra.DropBarVertical) { thisObject.height = objectsAreaHeight; } // Calculate the top offset for this object. This must take into account the height // of thisObject, so that we vertically center all of the within the div object var yOffsetFromTopSideOfDiv = (objectsAreaHeight / 2) - (thisObject.getHeight(false) / 2); textHTML += thisObject.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); // Prepare for where the next object will be positioned, to the right of this one. xOffsetFromLeftSideOfDiv += thisObject.getWidth(false); // + k_interDigitSpaceWidth; } // RIGHT PARENTHESES NOTE WARNING Not adjusting the height. var yOffsetFromTopSideOfDiv = (objectsAreaHeight / 2) - (this.rightParentheses.getHeight(false) / 2); textHTML += this.rightParentheses.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); xOffsetFromLeftSideOfDiv += this.rightParentheses.getWidth(false); if (g_isEnabledGroupingBrackets == true) { yOffsetFromTopSideOfDiv = (objectsAreaHeight / 2) - (this.bracketRight.getHeight(false) / 2); textHTML += this.bracketRight.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); xOffsetFromLeftSideOfDiv += this.bracketRight.getWidth(false); } textHTML += "" + "\n"; this.alreadyInToHTML = false; return textHTML; } this.findObjectById = function(idToFind) { //report("IN Algebra.GroupParentheses.findObjectById " + this.iD + " with " + this.objects.length + " objects in this.objects\n"); var returnedObject; returnedObject = Algebra.getObjectById(this.leftParentheses, idToFind); if (!returnedObject) { returnedObject = Algebra.getObjectById(this.rightParentheses, idToFind); } return returnedObject; } report("EXITING Algebra.GroupParentheses AT VERY END OF CONSTRUCTOR " + this.iD + " with " + this.objects.length + " objects in this.objects AFTER setting their parentIds.\n"); reportTabOut(); } // CONSTRUCTOR for class: GROUP SEQUENCE MULTIPLY (for both explicit and implicit multiplies) // Algebra.GroupSequenceMultiply = function(parentObject, parentObjectId, theObjects) { // This represents a set of one or more sequential explicit and/or implicit multiplications. // An explicit multiply is one which explicitly includes the multiple operator, '*'. // An implicit mutiply is one for which there is not an explicit multiplication symbol, e.g. A B implies A * B. // This actually handles any number of such multiplications in a sequence. // So A B * C becomes A . B * C (where the dot (.) indicates an Implicit Multiplication and the asterisl (*) // inidcate an explicit mmultiplication. // // Parameter: // theObjects: an array of objects, ALREADY INCLUDING the explicit or implict multiply objects. // Note: theObjects are expected to be only actual algebra objects, NOT drop bars. Drop bars get // added to the set of objects within this constructor. //report("IN Algebra.GroupSequenceMultiply with " + theObjects.length + " objects\n"); this.parentObject = parentObject; this.parentObjectId = parentObjectId; this.iD = "GroupSequenceMultiply" + Algebra.getNextId(); this.className = "Group Sequence Multiply"; this.value = ""; this.isClickSelectable = false; this.isGroupTypeObject = true; // Note that non-group type objects do not even have this property. if (!theObjects || (theObjects.length < 1)) { reportNullObject("Algebra.GroupSequenceMultiply(parentObjectId, theObjects)","theObjects"); return; } this.setChildrenObjects = function(objects) { //report("IN Algebra.GroupSequenceMultiply.setChildrenObjects()\n"); //reportTabIn(); //Algebra.showObjectArray("GroupSequenceMultiply objects for setting children", objects); // Remove any leading (extraneous) multiplication signs. //Algebra.removeLeadingMultiplicationSigns(objects); // Remove any trailing (extraneous) multiplication signs. while (true) { if ((objects[objects.length-1] instanceof Algebra.OperatorMultiply) || (objects[objects.length-1] instanceof Algebra.OperatorMultiplyImplicit)) { // Remove that multiplication sign objects.pop(); // Which removes the last item from the array. } else { break; } } //Algebra.showObjectArray("After removing TRAILING multiplication signs", objects); //Algebra.assureExplicitMultiplyBetweenConsecutiveNumbers(objects); //Algebra.showObjectArray("After assureExplicitMultiplyBetweenConsecutiveNumbers", objects); // Set the set of objects as a property of this object's instance. // NOTE We MUST create a new array and copy over the objects one-by-one // instead of just doing a this.objects = objects because the calling function MAY // empty out the original object array referenced by theObjects. // We also insert VerticalDropBars as appropriate. this.objects = new Array(); // Start with one DropBarVertical at the very beginning. this.objects.push(new Algebra.DropBarVertical(this, this.iD)); for (var index = 0 ; index < objects.length ; ++index) { var thisObject = objects[index]; thisObject.parentObject = this; thisObject.parentObjectId = this.iD; this.objects.push(thisObject); if (Algebra.isNonOperator(thisObject)) { // then add another DropBarVertical after the object we just added. this.objects.push(new Algebra.DropBarVertical(this, this.iD)); } } //Algebra.showObjectArray("GroupSequenceMultiply objects AFTER inserting drop bars", this.objects); //Algebra.showObjectArray("FINAL this.objects", this.objects); //reportTabOut(); } this.setChildrenObjects(theObjects); // We create the bracket objects EVEN IF we don't happen to be currently showing them. this.bracketLeft = new Algebra.BracketLeft(this.parentObject, this.parentObjectId, "Multiplication Group",this.iD); this.bracketRight = new Algebra.BracketRight(this.parentObject, this.parentObjectId, "Multiplication Group",this.iD); this.width = 0; this.height = 0; this.getWidthAndHeight = function(forceRecalc) { //report("IN Algebra.GroupSequenceMultiply.getWidthAndHeight with " + theObjects.length + " objects\n"); // Determine the maximum height and total width of the objects that comprise this group object. var numberOfObjects = this.objects.length; var maxHeight = 0; var totalWidth = 0; for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = this.objects[index]; //report("Getting object # " + index + " " + thisObject.className + " width = " + thisObject.getWidth(forceRecalc)+ "\n"); var thisObjectHeight = thisObject.getHeight(forceRecalc); if (thisObjectHeight > maxHeight) { maxHeight = thisObjectHeight; } // We have a special case where the object is an Implicit Multiply Operator AND Implicit Multiply Operators are NOTVISIBLE. // In that case we do NOT want to add the width of the operator AND we do NOT want to follow it with a k_interObjectSpaceWidth. var isInvisibleImplicitMultiplyOperator = (!g_showImplicitMultiplicationSigns && (thisObject instanceof Algebra.OperatorMultiplyImplicit)); if (!isInvisibleImplicitMultiplyOperator) { totalWidth += thisObject.getWidth(forceRecalc); } var isVerticalDropBar = thisObject instanceof Algebra.DropBarVertical; if (!Algebra.isNonOperator(thisObject) && !isVerticalDropBar && !isInvisibleImplicitMultiplyOperator) { totalWidth += k_interObjectSpaceWidth; } } //report("totalWidth = "+totalWidth+"\n"); // Remember the width and height for this Object. this.width = totalWidth; //+ (numberOfObjects - 1) * k_interObjectSpaceWidth; this.height = maxHeight; if (g_isEnabledGroupingBrackets == true) { //report("But also have group brackets LEFT = "+this.bracketLeft.getWidth(forceRecalc)+" RIGHT = "+this.bracketRight.getWidth(forceRecalc)+"\n"); this.width += this.bracketLeft.getWidth(forceRecalc) + this.bracketRight.getWidth(forceRecalc); if (this.bracketLeft.getHeight(forceRecalc) > this.height) { this.height = this.bracketLeft.getHeight(forceRecalc); } if (this.bracketRight.getHeight(forceRecalc) > this.height) { this.height = this.bracketRight.getHeight(forceRecalc); } } //report("this.width = "+this.width+"\n"); return {width:this.width, height:this.height}; }; this.getWidth = function(forceRecalc) { if (this.width == 0 || (forceRecalc == true)) { this.getWidthAndHeight(forceRecalc); } return this.width; } this.getHeight = function(forceRecalc) { if (this.height == 0 || (forceRecalc == true)) { this.getWidthAndHeight(forceRecalc); } return this.height; } this.isDragable = false; this.currentDisplayState = k_displayStateNormal; this.setDisplayState = function(displayState) { // Parameters: // displayState k_displayStateNormal, k_displayStateSelected, k_displayStateTarget, or k_displayStateHidden // Should ONLY call this AFTER the toHTML() method has been called // and the object was actually inserted into the page. // Tell all of the contained objects to setSelectState() this.currentDisplayState = displayState; for (var index = 0 ; index < this.objects.length ; ++index) { var object = this.objects[index]; if (object && object.setDisplayState) { object.setDisplayState(displayState); } } } this.toString = function() { var theString = reportTab() + "[>GROUP SEQUENCE MULTIPLY>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "isDraggable " + this.isDragable + "\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; theString += reportTab() + "consists of " + this.objects.length + " objects:\n"; reportTabIn(); if (g_isEnabledGroupingBrackets == true) { theString += this.bracketLeft.toString(); } for (var index = 0 ; index < this.objects.length ; ++index) { // NOTE should probably check to assure that it has a toString method!! theString += this.objects[index].toString(); } if (g_isEnabledGroupingBrackets == true) { theString += this.bracketRight.toString(); } reportTabOut(); reportTabOut(); theString += reportTab() + "[" + "\n"; var xOffsetFromLeftSideOfDiv = 0; var yOffsetFromTopSideOfDiv = 0; if (g_isEnabledGroupingBrackets == true) { yOffsetFromTopSideOfDiv = (objectsAreaHeight / 2) - (this.bracketLeft.getHeight(false) / 2); textHTML += this.bracketLeft.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); xOffsetFromLeftSideOfDiv += this.bracketLeft.getWidth(false); } // Set the position of each of the contained objects, so that they are // positioned relative to this div object. var numberOfObjects = this.objects.length; //reportTabIn(); for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = this.objects[index]; //report(thisObject.className+"\n"); // If it's a DropBarVertical, adjust its height. var isVerticalDropBar = thisObject instanceof Algebra.DropBarVertical; if (isVerticalDropBar) { thisObject.height = objectsAreaHeight; } // We have a special case where the object is an Implicit Multiply Operator AND Implicit Multiply Operators are NOT VISIBLE. // In that case we do NOT want to add the width of the operator AND we do NOT want to follow it with a k_interObjectSpaceWidth. var isInvisibleImplicitMultiplyOperator = (!g_showImplicitMultiplicationSigns && (thisObject instanceof Algebra.OperatorMultiplyImplicit)); // Calculate the top offset for this object. This must take into account the height // of thisObject, so that we vertically center along the centerline of this object. var yOffsetFromTopSideOfDiv = (objectsAreaHeight / 2) - (thisObject.getHeight(false) / 2); textHTML += thisObject.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); if (!isInvisibleImplicitMultiplyOperator) { // Prepare for where the next object will be positioned, to the right of this one. xOffsetFromLeftSideOfDiv += thisObject.getWidth(false); if (!Algebra.isNonOperator(thisObject) && !isVerticalDropBar) { xOffsetFromLeftSideOfDiv += k_interObjectSpaceWidth; } } } //reportTabOut(); if (g_isEnabledGroupingBrackets == true) { yOffsetFromTopSideOfDiv = (objectsAreaHeight / 2) - (this.bracketRight.getHeight(false) / 2); textHTML += this.bracketRight.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); xOffsetFromLeftSideOfDiv += this.bracketRight.getWidth(false); } textHTML += "" + "\n"; this.alreadyInToHTML = false; //reportTabOut(); return textHTML; } this.insertObject = function(objectToInsert, dropBar) { // This inserts the specified objectToInsert into the set of objects at the // position specified by the dropBar. // returns true if successful. //report("IN Algebra.GroupSequenceMultiply.insertObject(objectToInsert, dropBar)\n"); //reportTabIn(); if (!objectToInsert || (objectToInsert == null)) { reportNullObject("Algebra.GroupSequenceMultiply.insertObject(objectToInsert, dropBar)","objectToInsert"); //reportTabOut(); return false; } //report("objectToInsert = "+objectToInsert.iD+"\n"); if (!dropBar || (dropBar == null)) { reportNullObject("Algebra.GroupSequenceMultiplyinsertObject(objectToInsert, dropBar).insertObject","dropBar"); //reportTabOut(); return false; } //report("dropBar = "+dropBar.iD+"\n"); Algebra.showObjectArray("Original array of objects",this.objects); // Create a new object array, with the objectToInsert placed at the location as specified // by the dropBar. Also insert an extra multiplication object before the objectToInsert and // an extra dropbar after the object. //report("CREATING NEW COMPOSITE ARRAY\n"); //reportTabIn(); var numberOfObjects = this.objects.length; var newArray = new Array(); var insertedTheObject = false; var haveAlreadyEncounteredFirstNonDropBar = false; for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = this.objects[index]; //report("thisObject = "+thisObject.iD+"\n"); if (thisObject == dropBar) { //report("Which is where we need to insert the objectToInsert:\n"); //reportTabIn(); // This is where we insert the new object. // ORIGINAL DROP BAR //report("copy original drop bar\n"); newArray.push(thisObject); //i.e. the original dropBar. if (haveAlreadyEncounteredFirstNonDropBar) { // Then we're to the right of a prior object, so we need a multiply operator. // // NEW OPERATOR MULTIPLY var newOperatorMultiply = Algebra.getStandardOperatorMultiply(this.iD); //report("Insert newOperatorMultiply = "+newOperatorMultiply.iD+"\n"); newArray.push(newOperatorMultiply); } // THE NEW OBJECT TO INSERT //report("Insert the obejctToInsert = "+objectToInsert.iD+"\n"); objectToInsert.parentObject = this.iD; newArray.push(objectToInsert); if (!haveAlreadyEncounteredFirstNonDropBar) { // Then we're at the very first drop bar, so need a multiply operator to the // right of the just inserted new object. // // NEW OPERATOR MULTIPLY var newOperatorMultiply = Algebra.getStandardOperatorMultiply(this.iD); //report("Insert newOperatorMultiply = "+newOperatorMultiply.iD+"\n"); newArray.push(newOperatorMultiply); } // AN ADDITIONAL DROP BAR var newDropBar = new Algebra.DropBarVertical(this, this.iD); //report("Insert newDropBar = "+newDropBar.iD+"\n"); newArray.push(newDropBar); insertedTheObject = true; //reportTabOut(); } else { // This is NOT the place for the new objecToInsert, so just copy // over the obect to the newArray. //report("Copy to newArray\n"); newArray.push(thisObject); haveAlreadyEncounteredFirstNonDropBar = true; } } reportTabOut(); if (!insertedTheObject) { reportProgrammingError("Algebra.GroupSequenceMultiplyinsertObject(objectToInsert, dropBar).insertObject","Failed to locat specified dropBar."); //reportTabOut(); return false; } // replace the previous set of objects with our new set of objects. this.objects = newArray; //Algebra.showObjectArray("Modified array of objects",this.objects); //reportTabOut(); //report("EXIT FROM Algebra.GroupSequenceMultiply.insertObject(objectToInsert, dropBar)\n"); return true; } } Algebra.evaluateObject = function(object) { // This attempts to produce a numerical value for the specified object. // In the simplest case it's an Integer object and simply returns the value of the Integer. // In more complex cases it can be group sequence multiple, group sequence add/sub, group paren, fractions, etc. if (object instanceof Algebra.Integer) { return parseInt(object.value); } if (object instanceof Algebra.Variable) { reportProgrammingError("Algebra.evaluateObject(object)","object is a Variable.\nVariables are NOT handled by this function."); return 1; } var firstGroupWithContent = Algebra.getFirstGroupWithContentsDownTheHierarchy(object); //report("firstGroupWithContent = "+firstGroupWithContent.iD+"\n"); var objects = firstGroupWithContent.objects; var numberOfObjects = objects.length; if (firstGroupWithContent instanceof Algebra.GroupSequenceMultiply) { var product = 0; for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = objects[index]; if (!Algebra.isDropBar(thisObject) && !Algebra.isOperatorMultiply(thisObject)) { var valueThisObject = Algebra.evaluateObject(thisObject); if (product == 0) { product = valueThisObject; } else { product *= valueThisObject; } } } return product; } else if (firstGroupWithContent instanceof Algebra.GroupSequenceAddSubtract) { var total = 0; var operatorIsAdd = true; // false implies operatorIsMinus for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = objects[index]; if (!Algebra.isDropBar(thisObject)) { if (thisObject instanceof Algebra.OperatorPlus) { operatorIsAdd = true; } else if (thisObject instanceof Algebra.OperatorMinus) { operatorIsAdd = false; } else { var valueThisObject = Algebra.evaluateObject(thisObject); if (operatorIsAdd == 0) { total += valueThisObject; } else { total -= valueThisObject; } } } } return total; } reportProgrammingError("Algebra.evaluateObject(object)","object is a "+firstGroupWithContent.className+".\nWhich are NOT handled by this function."); return 1; } var k_interDivisionSpaceHeight = 2; var k_divisionLineExtraWidthLeftAndRight = 2; // CONSTRUCTOR for class: GROUP FRACTION // Algebra.GroupFraction = function(parentObject, parentObjectId, theObjectNumerator, theObjectDenominator) { //report("IN Algebra.GroupFraction(parentObjectId, "+theObjectNumerator.className+", "+theObjectDenominator.className+")\n"); //reportTabIn(); // This represents a division. // // Parameters: // theObjectNumerator: The object constituting the numerator (upper part) of the division. // theObjectDenominator: The object constituting the denominator (lower part) of the division. // // Note that we assume that parenthetical objects have been translated into single parenthesized objects // prior to handling the division objects. So we can pass in just the single object to the left and // the single object to the right of the division sign. // Note: theObjects are expected to be only actual algebra objects, NOT drop bars. Drop bars get // added to the set of objects within this constructor. this.parentObject = parentObject; this.parentObjectId = parentObjectId; this.iD = "GroupFraction" + Algebra.getNextId(); this.className = "Group Fraction"; this.value = ""; this.isClickSelectable = false; this.isGroupTypeObject = true; // Note that non-group type objects do not even have this property. if (!theObjectNumerator || (theObjectNumerator == null)) { reportNullObject("Algebra.GroupFraction()","theObjectNumerator"); //reportTabOut(); return; } if (!theObjectDenominator || (theObjectDenominator == null)) { reportNullObject("Algebra.GroupFraction()","theObjectDenominator"); //reportTabOut(); return; } this.objectNumerator = new Algebra.GroupNumerator(this, this.iD, theObjectNumerator); this.objectDenominator = new Algebra.GroupDenominator(this, this.iD, theObjectDenominator); //report("After grouping num and denom: objectNumerator = "+this.objectNumerator.className+" objectDenominator = "+this.objectDenominator.className+"\n"); Algebra.removeOuterParentheses(this.objectNumerator); Algebra.removeOuterParentheses(this.objectDenominator); //report("After removeOuterParentheses num and denom: objectNumerator = "+this.objectNumerator.className+" objectDenominator = "+this.objectDenominator.className+"\n"); this.objects = new Array(); this.objects.push(this.objectNumerator); this.objects.push(this.objectDenominator); this.divisionLine = new Algebra.OperatorDivisionLine(this, this.iD); // We create the bracket objects EVEN IF we don't happen to be currently showing them. this.bracketLeft = new Algebra.BracketLeft(this.parentObject, this.parentObjectId, "Fraction",this.iD); this.bracketRight = new Algebra.BracketRight(this.parentObject, this.parentObjectId, "Fraction",this.iD); this.width = 0; this.height = 0; this.getWidthAndHeight = function(forceRecalc) { // NOTE that the division object is positioned as numerator OVER denominator. // That is, it looks like: // // (ax + b) // -------- // 7 // // It is NOT like (ax + b) / 7, although this is how it looks in the origninal text of the equation. // var maxWidth = this.objectNumerator.getWidth(forceRecalc); if (this.objectDenominator.getWidth(forceRecalc) > maxWidth) { maxWidth = this.objectDenominator.getWidth(forceRecalc); } // The disvision line which is between the -numerator (above it) and the denominator (below it) // should extend a little bit to theleft and right beyond the (max) width of the numerator and denominator. maxWidth += (k_divisionLineExtraWidthLeftAndRight * 2); var totalHeight = this.objectNumerator.getHeight(forceRecalc) + this.objectDenominator.getHeight(forceRecalc) + this.divisionLine.getHeight(forceRecalc) + (2 * k_interDivisionSpaceHeight); // Remember the width and height for this object. this.width = maxWidth; this.height = totalHeight; // Force this instance of the divisionLine to be the correct width. this.divisionLine.width = this.width; if (g_isEnabledGroupingBrackets == true) { this.width += this.bracketLeft.getWidth(forceRecalc) + this.bracketRight.getWidth(forceRecalc); if (this.bracketLeft.getHeight(forceRecalc) > this.height) { this.height = this.bracketLeft.getHeight(forceRecalc); } if (this.bracketRight.getHeight(forceRecalc) > this.height) { this.height = this.bracketRight.getHeight(forceRecalc); } } return {width:this.width, height:this.height}; }; this.getWidth = function(forceRecalc) { if (this.width == 0 || (forceRecalc == true)) { this.getWidthAndHeight(forceRecalc); } return this.width; }; this.getHeight = function(forceRecalc) { if (this.height == 0 || (forceRecalc == true)) { this.getWidthAndHeight(forceRecalc); } return this.height; }; this.isDragable = false; this.currentDisplayState = k_displayStateNormal; this.setDisplayState = function(displayState) { // Parameters: // displayState k_displayStateNormal, k_displayStateSelected, k_displayStateTarget, or k_displayStateHidden // Should ONLY call this AFTER the toHTML() method has been called //and the object was actually inserted into the page. this.currentDisplayState = displayState; // Tell all of the contained objects to setSelectState() if (this.objectNumerator && this.objectNumerator.setDisplayState) { this.objectNumerator.setDisplayState(displayState); } if (this.objectDenominator && this.objectDenominator.setDisplayState) { this.objectDenominator.setDisplayState(displayState); } if (this.divisionLine && this.divisionLine.setDisplayState) { this.divisionLine.setDisplayState(displayState); } } this.toString = function() { var theString = reportTab() + "[>GROUP FRACTION>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "parentObjectId = " + this.parentObjectId + "\n"; theString += reportTab() + "height = " + this.height + "\n"; theString += reportTab() + "width = " + this.width + "\n"; theString += reportTab() + "isDraggable " + this.isDragable + "\n"; theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; if (g_isEnabledGroupingBrackets == true) { theString += this.bracketLeft.toString(); } theString += reportTab() + "numerator:\n"; reportTabIn(); theString += this.objectNumerator.toString(); reportTabOut(); theString += this.divisionLine.toString(); theString += reportTab() + "denominator:\n"; reportTabIn(); theString += this.objectDenominator.toString(); reportTabOut(); if (g_isEnabledGroupingBrackets == true) { theString += this.bracketRight.toString(); } reportTabOut(); theString += reportTab() + "[" + "\n"; var top = 0; var left; var objectsAreaHeight = this.getHeight(false); var xOffsetFromLeftSideOfDiv = 0; if (g_isEnabledGroupingBrackets == true) { yOffsetFromTopSideOfDiv = (objectsAreaHeight / 2) - (this.bracketLeft.getHeight(false) / 2); textHTML += this.bracketLeft.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); xOffsetFromLeftSideOfDiv += this.bracketLeft.getWidth(false); } // Position the NUMERATOR, at the top, horizontally centered. left = (totalWidth / 2) - (numeratorWidth / 2); textHTML += this.objectNumerator.toHTML(left, top, displayStateInitial); top += numeratorHeight + k_interDivisionSpaceHeight; // Position the DIVISION LINE below the numerator, occupying the entire width. if (g_isEnabledGroupingBrackets == true) { xOffsetFromLeftSideOfDiv = this.bracketLeft.getWidth(false); } textHTML += this.divisionLine.toHTML(xOffsetFromLeftSideOfDiv, top, displayStateInitial); top += this.divisionLine.getHeight(false) + k_interDivisionSpaceHeight; // Position the DENOMINATOR below the division line, horizontally centered. left = (totalWidth / 2) - (denominatorWidth / 2); textHTML += this.objectDenominator.toHTML(left, top, displayStateInitial); if (g_isEnabledGroupingBrackets == true) { xOffsetFromLeftSideOfDiv = this.getWidth(false) - this.bracketRight.getWidth(false); yOffsetFromTopSideOfDiv = (objectsAreaHeight / 2) - (this.bracketRight.getHeight(false) / 2); textHTML += this.bracketRight.toHTML(xOffsetFromLeftSideOfDiv, yOffsetFromTopSideOfDiv, displayStateInitial); } textHTML += "" + "\n"; this.alreadyInToHTML = false; return textHTML; } this.findObjectById = function(idToFind) { var returnedObject; returnedObject = Algebra.getObjectById(this.objectNumerator, idToFind); if (!returnedObject) { returnedObject = Algebra.getObjectById(this.objectDenominator, idToFind); if (!returnedObject) { returnedObject = Algebra.getObjectById(this.divisionLine, idToFind); } } return returnedObject; } this.getNumeratorNumericalValue = function() { var value = Algebra.evaluateObject(this.objectNumerator); return value; } this.getDenominatorNumericalValue = function() { // WARNING NOTE May need to extend this in case we have other types // of Denominator besides Integers that we want to be able to evaluate. var value = Algebra.evaluateObject(this.objectDenominator); return value; } } // CONSTRUCTOR for class EQUATION: // Algebra.Equation = function(tokens) { //report("IN Algebra.Equation(tokens) with:\n"+Algebra.getTokensNames(tokens)); // PROPERTIES: // // parentObjectId // className // value // isGroupTypeObject // objects // expressionSideLeft // expressionSideRight // equalsSign // isDragable // toString this.parentObjectId = null; this.className = "Equation"; this.iD = "Equation" + Algebra.getNextId(); this.value = "equation"; this.isClickSelectable = false; this.objects = new Array(); if (!(tokens && tokens.length > 0)) { // Programming error. We should never call this with an empty object. reportNullObject("Algebra.Equation()","tokens"); return; } // Split the token into a left-hand side part and a right-hand side part, doingthe // split at the location of the equals sign. // NOTE: We need to detect the presence of a SINGLE EQUALS SIGN during initial scanning. var positionEqualsSign = null; //var diagnosticString = ""; for (index = 0 ; index < tokens.length ; ++index) { var g_tokenType = Algebra.getTokenType(tokens[index]); //diagnosticString += "Checking #"+index+" = "+ if (g_tokenType == k_Algebra_tokenType_signEquals) { positionEqualsSign = index; break; } } if (positionEqualsSign == null) { // There is NO equals sign in the equation. reportProgrammingError("Algebra.Equation()","called with equation missing an equals sign"); return; } //report("Equals sign is at position "+positionEqualsSign); // We seem to know where the equals sign is. var tokensLeftSide = tokens.slice(0,positionEqualsSign); var tokensRightSide = tokens.slice(positionEqualsSign+1, tokens.length); //report("AFTER SPLITTING INTO LEFT AND RIGHT SIDES:\n\nLEFT SIDE = \n" // + Algebra.getTokensNames(tokensLeftSide) // + "\n\nRIGHT SIDE = \n" // + Algebra.getTokensNames(tokensRightSide)); //report("ABOUT TO this.expressionSideLeft = new Algebra.TranslateTokensIntoAlgebraObjects(this, this.iD, tokensLeftSide);\n"); // this.expressionSideLeft = new Algebra.TranslateTokensIntoAlgebraObjects(this, this.iD, tokensLeftSide); var algebraObjects = Algebra.TranslateTokensIntoAlgebraObjects(this.iD, tokensLeftSide); //report("Back from this.expressionSideLeft = new Algebra.TranslateTokensIntoAlgebraObjects(this, this.iD, tokensLeftSide);\n"); //report("this.expressionSideLeft has " + this.expressionSideLeft.objects.length + " objects in it.\n"); // Group the expression within a Group Expression Left //report("Group this.expressionSideLeft within a Group Expression Left\n"); // this.expressionSideLeft = new Algebra.GroupExpressionLeft(this, this.iD, this.expressionSideLeft.objects); this.expressionSideLeft = new Algebra.GroupExpressionLeft(this, this.iD, algebraObjects); //report("AFTER call to new Algebra.GroupExpressionLeft(this, this.expressionSideLeft has "+this.expressionSideLeft.objects.length+" objects\n"); //report("BEFORE call to removeOuterParentheses this.expressionSideLeft has "+this.expressionSideLeft.objects.length+" objects\n"); Algebra.removeOuterParentheses(this.expressionSideLeft); //report("AFTER call to removeOuterParentheses this.expressionSideLeft has "+this.expressionSideLeft.objects.length+" objects\n"); //report("BACK FROM Group this.expressionSideLeft within a GROUP SEQUENCE ADD SUBTRACT\n"); //report("this.expressionSideLeft now = :\n" + this.expressionSideLeft.toHTML() + "\n"); //report("RESULT of converting to objects:\n" + this.expressionSideLeft.toString() + "\n"); // this.expressionSideRight = new Algebra.TranslateTokensIntoAlgebraObjects(this, this.iD, tokensRightSide); algebraObjects = Algebra.TranslateTokensIntoAlgebraObjects(this.iD, tokensRightSide); // Group the expression within a Group Expression Right // this.expressionSideRight = new Algebra.GroupExpressionRight(this, this.iD, this.expressionSideRight.objects); this.expressionSideRight = new Algebra.GroupExpressionRight(this, this.iD, algebraObjects); Algebra.removeOuterParentheses(this.expressionSideRight); this.objects.push(this.expressionSideLeft); this.objects.push(this.expressionSideRight); //report("RESULT of converting to objects:\n" + this.expressionSideRight.toString() + "\n"); this.equalsSign = new Algebra.EqualsSign(this, this.iD); this.isDragable = false; this.currentDisplayState = k_displayStateNormal; this.setDisplayState = function(displayState) { // Parameters: // displayState k_displayStateNormal, k_displayStateSelected, k_displayStateTarget, or k_displayStateHidden // Should ONLY call this AFTER the toHTML() method has been called //and the object was actually inserted into the page. // Tell all of the contained objects to setSelectState() this.currentDisplayState = displayState; if (this.expressionSideLeft && this.expressionSideLeft.setDisplayState) { this.expressionSideLeft.setDisplayState(displayState); } if (this.expressionSideRight && this.expressionSideRight.setDisplayState) { this.expressionSideRight.setDisplayState(displayState); } } this.toString = function() { var theString = reportTab() + "[>EQUATION>]\n"; reportTabIn(); theString += reportTab() + "iD = " + this.iD + "\n"; theString += reportTab() + "isDraggable " + this.isDragable + "\n"; theString += reportTab() + "EXPRESSION LEFT SIDE\n"; theString += reportTab() + this.expressionSideLeft.toString(); theString += reportTab() + "EXPRESSION RIGHT SIDE\n"; theString += reportTab() + this.expressionSideRight.toString(); theString += reportTab() + "Display State = " + Algebra.displayState(this.currentDisplayState) + "\n"; reportTabOut(); theString += reportTab() + "[>>>>Processing " + expressionTokens.length + " tokens\n"); //report(">>>>>Translating the tokens (i.e. the strings) into Algebra objects, e.g. Algebra.Integer."); var objects = new Array(); var dummyParentId = "DUMMY ID"; for (var index = 0 ; index < expressionTokens.length ; ++index) { var thisToken = expressionTokens[index]; var thisTokenType = Algebra.getTokenType(thisToken); var thisTokenTypeName = Algebra.getTokenTypeName(thisTokenType); //report("Translating token number " + index + " = " + thisToken + " type = [" + thisTokenTypeName + "]\ninto corresponding Algebra object\n"); //report("Processing "+thisToken+" is a "+thisTokenTypeName); switch (thisTokenType) { case k_Algebra_tokenType_unknown: { // NOTE we really shold not encounter this since we tested // for unknowns above and bypass this section when we find any. // For now, just ignore it. But really should report a programming error. reportProgrammingError("Algebra.Expression", "Unknown Token Type Encountered"); return null; //break; } case k_Algebra_tokenType_number: { //report("IN case k_Algebra_tokenType_number\n"); // an Integer token gets turned into an Integer class instance containing // one or more Digit class instances. objects.push(new Algebra.Integer(null, dummyParentId, thisToken)); break; } case k_Algebra_tokenType_signEquals: { //report("IN case k_Algebra_tokenType_signEquals\n"); objects.push(new Algebra.EqualsSign(null, dummyParentId)); break; } case k_Algebra_tokenType_operatorPlus: { //report("IN case k_Algebra_tokenType_operatorPlus\n"); objects.push(new Algebra.OperatorPlus(null, dummyParentId)); break; } case k_Algebra_tokenType_operatorMinus: { //report("IN case k_Algebra_tokenType_operatorMinus\n"); objects.push(new Algebra.OperatorMinus(null, dummyParentId)); break; } case k_Algebra_tokenType_operatorMultiply: { //report("IN case k_Algebra_tokenType_operatorMultiply\n"); objects.push(new Algebra.OperatorMultiply(null, dummyParentId)); break; } case k_Algebra_tokenType_operatorDivide: { //report("IN case k_Algebra_tokenType_operatorDivide\n"); objects.push(new Algebra.OperatorDivide(null, dummyParentId)); break; } case k_Algebra_tokenType_operatorPower: { //report("IN case k_Algebra_tokenType_operatorPower\n"); objects.push(new Algebra.OperatorPower(null, dummyParentId)); break; } case k_Algebra_tokenType_operatorRoot: { //report("IN case k_Algebra_tokenType_operatorRoot\n"); objects.push(new Algebra.OperatorRoot(null, dummyParentId)); break; } case k_Algebra_tokenType_variable: { //report("IN case k_Algebra_tokenType_variable\n"); objects.push(new Algebra.Variable(null, dummyParentId, thisToken)); break; } case k_Algebra_tokenType_parenLeft: { //report("IN case Algebra_tokenType_ParenLeft\n"); objects.push(new Algebra.ParenthesesLeft(null, dummyParentId)); break; } case k_Algebra_tokenType_parenRight: { //report("IN case k_Algebra_tokenType_parenRight\n"); objects.push(new Algebra.ParenthesesRight(null, dummyParentId)); break; } default: { reportProgrammingError("Algebra.Expression", "Undefined Token Type Encountered"); return null; } } } //report("<<<< 1) { // This is a nested parentheses, so push it onto our enclosedObjects array // so that it can get preocessed when we recursivley call Algebra.groupifyExpressionObjects(enclosedObjects), below, // when we encounter the matching (outermost) parentheses of the parenthetical group we are currently handling. enclosedObjects.push(object); } } else if (object instanceof Algebra.ParenthesesRight) { // // HIT RIGHT PARENTHESES // //report("Hit a right parentheses, parenthesesCount is " + parenthesesCount + "\n"); // We hit a right parentheses. if (parenthesesCount <= 0) { // We have a mismatched right parentheses. // NOTE. We really need a way of reporting this to the actual USER!!! // But for now, at least show in diagnostics. adviseUserExpressionError("MISMATCHED PARENTHESES, DANGLING RIGHT PAREN\n"); //alert("ERROR IN Algebra.groupifyExpressionObjects: MISMATCHED PARENTHESES, DANGLING RIGHT PAREN\n"); return null; } --parenthesesCount; //report("decrementing parenthesesCount to " + parenthesesCount + "\n"); if (parenthesesCount != 0) { // This is a nested right parentheses, so push it onto our enclosedObjects array. enclosedObjects.push(object); } else { // parenthesesCount is back to ZERO // // MATCHING OUTER-MOST RIGHT PARENTHESES // //report("It is a matching right parentheses\n"); // We now want to create a GroupParentheses object and put that into our expressionObjectsGroupified. // Note that the enclosedObjects (i.e. those between the openning and closing parentheses) may // themselves include nested parentheses (or other types of groupings), so we also do a grouping on those, and pass the result // of that grouping to the Algebra.GroupParentheses. This uses recursion to accomplish this. Pretty neat, huh!? if (enclosedObjects.length > 0) { // There are indeed some objects inside these parentheses. //report("Recursively calling Algebra.groupifyExpressionObjects\n"); enclosedObjects = Algebra.groupifyExpressionObjects(enclosedObjects); if (enclosedObjects && enclosedObjects.length > 0) { // And the recursive calling of Algebra.groupifyExpressionObjects resulted in some objects. // So we create a GroupParentheses object for these enclosed objects and // add it to our expressionObjectsGroupified //report("IN Algebra.groupifyParenthesizedObjects about to create a new GroupParentheses object.\n"); var newParenthesizedObject = new Algebra.GroupParentheses(object.parentObject, parentObjectId, enclosedObjects); expressionObjectsGroupified.push(newParenthesizedObject); //report("IN Algebra.groupifyParenthesizedObjects after creating newParenthesizedObject it has " + newParenthesizedObject.objects.length + " objects\n"); } else { // The recursive call to Algebra.groupifyExpressionObjects resulted in empty content!! // NOTE. We really need a way of reporting this to the actual USER!!! // But for now, at least show in diagnostics. report("ERROR IN Algebra.groupifyExpressionObjects: Parentheses have no content.\n"); alert("ERROR IN Algebra.groupifyExpressionObjects: Parentheses have no content.\n"); return null; } } else { // It appears that there was nothing between the parentheses. // NOTE need a way to report this to the user. adviseUserExpressionError("Empty parentheses encountered\n"); //alert("ERROR IN Algebra.groupifyExpressionObjects: Empty parentheses encountered\n"); return null; } // Effectively empty out the enclosedObjects array. enclosedObjects.length = 0; } } else { // The object is neither a left nor a right parentheses. //report("The object is neither a left nor a right parentheses.\n"); if (parenthesesCount > 0) { //report("But we are inside a parenthesized section. so..\n"); // But we are inside a parenthesized section. // So we DO NOT add this to our expressionObjectsGroupified but rather store it // temporarily into our enclosedObjects array. //report(">> PUSHING >> the object onto enclosedObjects.\n"); enclosedObjects.push(object); } else { // We have a non-paren object and we're not currently within a parenthesized section of objects. // Just copy it over to the expressionObjectsGroupified array, //report(">> PUSHING >> the object onto expressionObjectsGroupified.\n"); expressionObjectsGroupified.push(object); } } } return expressionObjectsGroupified; } Algebra.insertImplicitMultiplyObjects = function(expressionObjects) { // We look through the expressionObjects ad when we find consecutive non-operators we // assume it means multiplication and we insert an OperatorMultiplyImplicit between then. //report("\n\n===================================================================\n"); //report(" INSERT IMPLICIT MULTIPLIES\n"); //report("===================================================================\n\n"); if (!expressionObjects) { reportNullObject("insertImplicitMultiplyObjects(expressionObjects)","expressionObjects"); alert("Algebra.insertImplicitMultiplyObjects(expressionObjects)","expressionObjects"); return null; } //report("Processing " + expressionObjects.length + " objects\n"); var expressionObjectsWithImplicitMultipliesInserted = new Array(); numberOfExpressionObjects = expressionObjects.length; for (var index = 0 ; index < numberOfExpressionObjects ; ++index) { var object = expressionObjects[index]; //report("Processing item # " + index + " = " + object.className + " = " + object.value + "\n"); var objectToItsRight = null; // Get the object to its right, if there is one. if (index < (numberOfExpressionObjects - 1)) { objectToItsRight = expressionObjects[index + 1] if (Algebra.isNonOperator(object) && (Algebra.isNonOperator(objectToItsRight))) { // We have two consecutive objects with no operator between them. // We interpret that as implying multiplication. //report("We have two consecutive non-operators, >> PUSHING >> the object and an Operator Implicit Multiply.\n"); expressionObjectsWithImplicitMultipliesInserted.push(object); expressionObjectsWithImplicitMultipliesInserted.push(new Algebra.OperatorMultiplyImplicit(null, k_dummyParentId)); } else { // We do NOT have two consecutive non-operators, so just push this object. //report("We do NOT have two consecutive non-operators, >> PUSHING >> just the object.\n"); expressionObjectsWithImplicitMultipliesInserted.push(object); } } else { // No object to its right, so just push this one and we're done. //report("No object to its right, just >> PUSHING >> the object.\n"); expressionObjectsWithImplicitMultipliesInserted.push(object); break; } } return expressionObjectsWithImplicitMultipliesInserted; } Algebra.groupifyDivisionObjects = function(expressionObjects) { // We look through the expressionObjectsMultiplyImplicitGrouped and when we encounter an operatorDivision object we gather up the // single object to its left and the single object to its right and construct an Algebra.GroupFraction object . //report("\n\n===================================================================\n"); //report(" GROUPIFYING DIVISION OBJECTS\n"); //report("===================================================================\n\n"); if (!expressionObjects) { reportNullObject("Algebra.groupifyDivisionObjects(expressionObjects)","expressionObjects"); alert("Algebra.groupifyDivisionObjects(expressionObjects)","expressionObjects"); return null; } //report("Processing " + expressionObjects.length + " objects\n"); var expressionObjectsGroupified = new Array(); var parentObjectId = k_dummyParentId; for (var index = 0 ; index < expressionObjects.length ; ++index) { var object = expressionObjects[index]; //report("Processing object # " + index + "\n" + object.toString() + "\n"); if (object instanceof Algebra.OperatorDivide) { //report("Hit a division sign\n"); parentObjectId = object.parentObjectId; // Since we presumably had already pushed the object which was to the left of this division // sign into our expressionObjectsGroupified array, pop off that object. if (index == 0) { // This is the very first object and it's a division sign. That is incorrect syntax. // NOTE should tell the user!! adviseUserExpressionError("Misplaced division sign."); //report("IN Algebra.groupifyDivisionObjects encountered misplaced (index = 0) division sign.\n"); return null; } //report("Getting object to left\n"); var objectToLeftOfDivisionSign = expressionObjectsGroupified.pop(); // And the object to the right of the division sign (that we just got from the expressionObjectsGroupified array // should also be the next item in that array. So get it. //report("Getting object to right\n"); var objectToRightOfDivisionSign = expressionObjects[index+1]; // Then point at the next object AFTER that one, so we don't encounter it again the next time through this loop. ++index; if (!objectToLeftOfDivisionSign) { adviseUserExpressionError("Missing numerator for fraction."); //report("ERROR IN Algebra.groupifyExpressionObjects: Missing left expresssion for division sign.\n"); return null; } if (!objectToRightOfDivisionSign) { adviseUserExpressionError("Missing denominator for fraction."); //report("ERROR IN Algebra.groupifyExpressionObjects: Missing right expression for division sign.\n"); return null; } // We have both the left and the right objects (which may be compound objects). // This should actually be done inside the Algebra.GroupFraction object constructor. // var objectsNumerator = new Array(); // objectsNumerator.push(objectToLeftOfDivisionSign); // objectToLeftOfDivisionSign = new Algebra.GroupNumerator(??,parentObjectId, objectsNumerator); // var objectsDenominator = new Array(); // objectsDenominator.push(objectToRightOfDivisionSign); // objectToRightOfDivisionSign = new Algebra.GroupNumerator(??,parentObjectId, objectsDenominator); // Now create the composite GroupFraction object and put it on to the expressionObjectsGroupified. //report("Creating composite Algebra.GroupFraction object.\n"); expressionObjectsGroupified.push(new Algebra.GroupFraction(object.parentObject, parentObjectId, objectToLeftOfDivisionSign, objectToRightOfDivisionSign)); } else { // The object is not a division sign. // So we just push it. expressionObjectsGroupified.push(object); } } return expressionObjectsGroupified; } Algebra.groupifyExpressionObjects = function(expressionObjects) { // This function takes in an array of expressionObjects and appropriately groups the objects. // For example, a left parentheses object followed by some sequence of objects followed by its closing right parentheses // should get turned into a single groupParenthese object, with the enclosed sequence bing children of that groupParentheses // object. Non-groupable objects remain ungrouped. // It returns the newly grouped array. //report("IN Algebra.groupifyExpressionObjects(expressionObjects)\n"); //report("\n\n===================================================================\n"); //report(" GROUPIFYING EXPRESSION OBJECTS\n"); //report("===================================================================\n\n"); if (!expressionObjects || (expressionObjects.length == 0)) { reportNullObject("Algebra.groupifyExpressionObjects","expressionObjects"); return; } //report("Processing " + expressionObjects.length + " Objects\n"); ///////////////////////////// // PARENTHESES ///////////////////////////// // Groupify the Parenthesized objects. expressionObjects = Algebra.groupifyParenthesizedObjects(expressionObjects); if (!expressionObjects) { alert("expressionObjects = Algebra.groupifyParenthesizedObjects(expressionObjects)\nresulted in NULL object"); } ///////////////////////////////////////// // INSERT IMPLICIT MULTIPLY OBJECTS ///////////////////////////////////////// expressionObjects = Algebra.insertImplicitMultiplyObjects(expressionObjects); //////////////////////////////// // MULTIPLY OBJECTS //////////////////////////////// // Groupify the Multiply and Implicit Multiply objects where necessary. expressionObjects = Algebra.groupifyExplicitAndImplicitMultiplyObjects(expressionObjects); if (!expressionObjects) { alert("expressionObjects = Algebra.groupifyExplicitAndImplicitMultiplyObjects(expressionObjects)\nresulted in NULL object"); } //////////////////////////////// // DIVISION OBJECTS //////////////////////////////// // Groupify the DIVISION objects.' expressionObjects = Algebra.groupifyDivisionObjects(expressionObjects); if (!expressionObjects) { alert("expressionObjects = Algebra.groupifyDivisionObjects(expressionObjects)\nresulted in NULL object"); } //////////////////////////////// // SEQUENCE (ADD and SUBTRACT) OBJECTS //////////////////////////////// // Groupify the addition and subtraction objects where necessary. expressionObjects = Algebra.groupifySequenceObjects(expressionObjects); if (!expressionObjects) { alert("expressionObjects = Algebra.groupifySequenceObjects(expressionObjects)\nresulted in NULL object"); } //report("EXITING FROM Algebra.groupifyExpressionObjects(expressionObjects)\n"); return expressionObjects; } Algebra.groupifyExplicitAndImplicitMultiplyObjects = function(expressionObjects) { //report("\n\n===================================================================\n"); //report(" GROUPIFYING EXPLICIT AND IMPLICIT MULTIPLY OBJECTS\n"); //report("===================================================================\n\n"); // At this point we expect all implict multiplies ( A B ) to have been converted to "actual" implicit // multiplies ( A . B ) i.e. with an OperatorMultiplyImplicit inserted appropriately. if (!expressionObjects) { reportNullObject("Algebra.groupifyExplicitAndImplicitMultiplyObjects(expressionObjects)","expressionObjects"); alert("Algebra.groupifyExplicitAndImplicitMultiplyObjects(expressionObjects)","expressionObjects"); return null; } //report("\nIN Algebra.groupifyExplicitAndImplicitMultiplyObjects(expressionObjects) with " + expressionObjects.length + " objects.\n"); if (expressionObjects.length == 1) { //report("EXITING FROM Algebra.groupifyExplicitAndImplicitMultiplyObjects(expressionObjects) with " + expressionObjects.length + "objects\n"); return expressionObjects; } //report("Processing " + expressionObjects.length + " Objects, consisting of\n"); // DIAGNOSE START // reportTabIn(); // for (index = 0 ; index < expressionObjects.length ; ++index) // { // var object = expressionObjects[index]; // report(object.value + "\n"); // } // reportTabOut(); // DIAGNOSE END var expressionObjectsGroupified = new Array(); var parentObjectId = 0; // We look through the expressionObjects and when we encounter either an explicit multiply operator // or an implicit multiply operator // then we gather up the sequence of one or more multiplies and group them together into a GroupSequenceMultiply object. // var numberOfExpressionObjects = expressionObjects.length; var index = 0; for (index = 0 ; index < numberOfExpressionObjects ; ++index) { var isOperatorMultiply = false; var objectCurrent = expressionObjects[index]; var objectToItsLeft = null; var objectToItsRight = null; // Gather up information about the objects to the left and right of the current object. if (index > 0) { objectToItsLeft = expressionObjects[index - 1]; } // otherwise we are at the very beginnning of the array so there's nothing to its left // and we're just left with objectToItsLeft = null, which is OK. if (index < numberOfExpressionObjects - 1) { objectToItsRight = expressionObjects[index + 1]; } // otherwise we are at the very last object in the array so there's nothing to its rght // and we're just left with objectToItsRight = null, which is OK. //report("Processing expression object # " + index + " " + objectCurrent.className + " = " + objectCurrent.value + "\n"); // Determine whether we're dealing with an explicit or implicit multiplcation. if ((objectCurrent instanceof Algebra.OperatorMultiply) || (objectCurrent instanceof Algebra.OperatorMultiplyImplicit)) { // We encountered a multiply operator. But we need non-operators to its left and right. //report("Which is a multiply operator\n"); if ((objectToItsLeft != null) && Algebra.isNonOperator(objectToItsLeft) && (objectToItsRight != null) && Algebra.isNonOperator(objectToItsRight)) { //report("and has valid non-operators to its left and right.\n"); //report("Hit need for gouping a sequence of one or more multiplication operations.\n"); // But the non-operator to its left had already been pushed onto expressionObjectsGroupified , // pop it off of expressionObjectsGroupified. //report("But the non-operator to the left of this first-in-sequence multiplier had already been pushed onto expressionObjectsGroupified.\n"); //report("pop it off of expressionObjectsGroupified.\n"); expressionObjectsGroupified.pop(); isOperatorMultiply = true; } else { // We did not find non-operators to the left and right. //report("Misplaced explicit or implicit multiplication operator encountered\n"); //alert("Misplaced explicit or implicit multiplication operator encountered\n"); adviseUserExpressionError("Misplaced multiplication operator encountered."); return null; } } if (isOperatorMultiply) { var sequenceOfExplicitAndImplicitMultiplyObjects = Array(); var parentObjectId = objectCurrent.parentObjectId; // We have either an explicit or an implicit multiply. // But there MAY actually be more than just one of them in sequence. // So we save them on a local array, pushing sequential explicit and/or implicit multiplies nto it // until we encounter an operator object or the end of the array of expressionObjects. //report("Encountered FIRST OPERATOR MULTIPLY in possible sequence of multiply operators\n"); //report(">> PUSHING >> object to left:\n" + objectToItsLeft + "\n"); sequenceOfExplicitAndImplicitMultiplyObjects.push(objectToItsLeft); //report(">> PUSHING >> multiply object:\n" + objectCurrent + "\n"); sequenceOfExplicitAndImplicitMultiplyObjects.push(objectCurrent); // Which is the OperatorMultiply OR OperatorMultiplyImplicit object. //report(">> PUSHING >> object to right:\n" + objectToItsRight + "\n"); sequenceOfExplicitAndImplicitMultiplyObjects.push(objectToItsRight); //report("Now entering loop to gather sequence of multiplications.\n"); // Not sure if I need this... var indexOfLastExplicitOrImplicitMultipliedObject = index + 1; // i.e. the current objectToItsRight. // Work our way through the objects, gathering up subsequent explicit or implicit multiply objects // until we encounter the end of the sequence. //report(reportTab()+"INTO FOR LOOP.\n"); for (var ndx = index + 2 ; ndx < numberOfExpressionObjects ; ++ndx) { var objectCurrentInSequence = expressionObjects[ndx]; //report("Next object in sequence = " + objectCurrentInSequence.className + " " + objectCurrentInSequence.value + "\n"); objectToItsRight = null; if (ndx < numberOfExpressionObjects - 1) { objectToItsRight = expressionObjects[ndx + 1]; //report("objectToItsRight = " + objectToItsRight + "\n"); } // otherwise we are at the very last object in the array so there's nothing to its right // and we're just left with objectToItsRight = null, which is OK. if (Algebra.isNonOperator(objectCurrentInSequence)) { // We hit a non-operator. So we're done gathering up the sequence of multiplys. //report("objectCurrentInSequence is a Non-Operator, so exiting from loop.\n"); indexOfLastExplicitOrImplicitMultipliedObject = ndx - 1; break; } else { // We hit an operator type of object. //report("We have an operator as the object.\n"); if ((objectCurrentInSequence instanceof Algebra.OperatorMultiply) || (objectCurrentInSequence instanceof Algebra.OperatorMultiplyImplicit)) { //report("Another Multiply.\n"); // Then this is ANOTHER multiplied object, so save it for our sequence of objects. // Of course, this also needs a non-operator to its right in order to be a valid // multiplier. if ((objectToItsRight != null) && Algebra.isNonOperator(objectToItsRight)) { //report("which means another valid multiply\n"); // Then yes indeed we have another valid multiplication. //report(">> PUSHING >>\n" + objectCurrentInSequence + "\n"); sequenceOfExplicitAndImplicitMultiplyObjects.push(objectCurrentInSequence); // Which is the explicit Operator Multiply. //report(">> PUSHING >>\n" + objectToItsRight + "\n"); sequenceOfExplicitAndImplicitMultiplyObjects.push(objectToItsRight); // Point at the next object to the right of the object which is to the right of the multiply object. ++ndx; indexOfLastExplicitOrImplicitMultipliedObject = ndx + 1; } else { // The object to the right of the multiply operator is an operator. // We appear to have hit a dangling multiplier at the end of the expression. adviseUserExpressionError("Misplaced multiplication operator encountered."); return null; // NOTE WARNING. Dang, how do we handle something like A * B * -2 ???? // MAYBE specifically look for the minus sign, and maybe also the plus sign? } } else { // We hit an operator that is NOT an Algebra.OperatorMultiply. //report("It is an operator but NOT an Operator Multiply\nExiting from gathering loop."); indexOfLastExplicitOrImplicitMultipliedObject = ndx - 1; // So we're done looking for sequential explicit and implicit multiplied objects. break; } } } //report("OUT OF LOOP\n"); // Replace the sequence of objects with an Algebra.GroupSequenceMultiply object //report("Replace the sequence of objects with an Algebra.GroupSequenceMultiply object.\n"); expressionObjectsGroupified.push(new Algebra.GroupSequenceMultiply(objectCurrent.parentObject, parentObjectId, sequenceOfExplicitAndImplicitMultiplyObjects)); index = indexOfLastExplicitOrImplicitMultipliedObject; // This will get indexed PAST the one we just handled // at the next "for" loop, above. } else { //report("Not a multiply. Just push to expressionObjectsGroupified\n"); // The currentObject is for neither an explicit multiply nor an implicit multiply. // So just push it into our expressionObjectsGroupified. expressionObjectsGroupified.push(objectCurrent); } } //report("EXITING FROM Algebra.groupifyExplicitAndImplicitMultiplyObjects(expressionObjects) with " + expressionObjectsGroupified.length + "objects\n"); //alert("EXITING FROM Algebra.groupifyExplicitAndImplicitMultiplyObjects(expressionObjects) with " + expressionObjectsGroupified.length + "objects\n"); return expressionObjectsGroupified; } Algebra.groupifySequenceObjects = function(expressionObjects) { //report("\n\n===================================================================\n"); //report(" GROUPIFYING SEQUENCE (ADD & SUBTRACT) OBJECTS\n"); //report("===================================================================\n\n"); //report("\nIN Algebra.groupifySequenceObjects(expressionObjects) with " + expressionObjects.length + " objects.\n"); if (!expressionObjects) { reportNullObject("Algebra.groupifySequenceObjects","expressionObjects"); alert("Algebra.groupifySequenceObjects","expressionObjects"); return; } if (expressionObjects.length == 1) { //report("EXITING FROM Algebra.groupifySequenceObjects(expressionObjects) with " + expressionObjects.length + "objects\n"); return expressionObjects; } //report("Processing " + expressionObjects.length + " Objects, consisting of\n"); // DIAGNOSE START // reportTabIn(); // for (index = 0 ; index < expressionObjects.length ; ++index) // { // var object = expressionObjects[index]; // report(object.value + "\n"); // } // reportTabOut(); // DIAGNOSE END var expressionObjectsGroupified = new Array(); var parentObjectId = 0; // We look through the expressionObjects and when we encounter either an addition or a subtraction operator // then we gather up the sequence of one or more adds/subtract and group them together into a GroupSequenceAddSubtract object. // var numberOfExpressionObjects = expressionObjects.length; var index = 0; for (index = 0 ; index < numberOfExpressionObjects ; ++index) { var isOperatorAddOrSubtract = false; var objectCurrent = expressionObjects[index]; var objectToItsLeft = null; var objectToItsRight = null; var isNonOperatorToLeft = false; var isNonOperatorToRight = false; // Gather up information about the objects to the left and right of the current object. if (index > 0) { objectToItsLeft = expressionObjects[index - 1]; } // otherwise we are at the very beginnning of the array so there's nothing to its left // and we're just left with objectToItsLeft = null, which is OK. if (index < numberOfExpressionObjects - 1) { objectToItsRight = expressionObjects[index + 1]; } // otherwise we are at the very last object in the array so there's nothing to its rght // and we're just left with objectToItsRight = null, which is OK. //report("Processing expression object # " + index + " " + objectCurrent.className + " = " + objectCurrent.value + "\n"); // Determine whether we're dealing with an add or subtract. if ((objectCurrent instanceof Algebra.OperatorPlus) || (objectCurrent instanceof Algebra.OperatorMinus)) { // We encountered an add or subtract operator. But we need non-operators to its left and right. //report("Which is an add or subtract operator\n"); isNonOperatorToLeft = (objectToItsLeft != null) && Algebra.isNonOperator(objectToItsLeft); isNonOperatorToRight = (objectToItsRight != null) && Algebra.isNonOperator(objectToItsRight); if (isNonOperatorToLeft || isNonOperatorToRight) { // The add or subtract operator has at least one valid non-operators to its left or right. // So we have hit a need for grouping a sequence of one or more add or subtract operations. //report("Hit need for gouping a sequence of one or more add or subtract operations.\n"); //report("and has valid non-operators to its left and right.\n"); if (isNonOperatorToLeft) { // But the non-operator to its left had already been pushed onto expressionObjectsGroupified , // pop it off of expressionObjectsGroupified. //report("But the non-operator to the left of this first-in-sequence multiplier had already been pushed onto expressionObjectsGroupified.\n"); //report("pop it off of expressionObjectsGroupified.\n"); expressionObjectsGroupified.pop(); } isOperatorAddOrSubtract = true; } else { // We did not find non-operators to the left and right. // WARNING NOTE: Maybe this is where we need to check for unary plus or minus!!!! adviseUserExpressionError("Misplaced "+objectCurrent.value+" operator encountered."); return null; } } if (isOperatorAddOrSubtract) { var sequenceOfAddSubtractObjects = Array(); var parentObjectId = objectCurrent.parentObjectId; // We have either an add or subtract operation. // But there MAY actually be more than just one of them in sequence. // So we save them on a local array, pushing sequential adds and subtract // until we encounter a non add/subtract operator object or the end of the array of expressionObjects. //report("Encountered FIRST OPERATOR ADD or SUBTRACT in possible sequence of add/subract operators\n"); //report(">> PUSHING >> object to left:\n" + objectToItsLeft + "\n"); if (isNonOperatorToLeft) { sequenceOfAddSubtractObjects.push(objectToItsLeft); } //report(">> PUSHING >> multiply object:\n" + objectCurrent + "\n"); sequenceOfAddSubtractObjects.push(objectCurrent); // Which is the OperatorMultiply OR OperatorMultiplyImplicit object. //report(">> PUSHING >> object to right:\n" + objectToItsRight + "\n"); sequenceOfAddSubtractObjects.push(objectToItsRight); //report("Now entering loop to gather sequence of multiplications.\n"); // Not sure if I need this... var indexOfLastAddOrSubtractObject = index + 1; // i.e. the current objectToItsRight. // Work our way through the objects, gathering up subsequent add or subtract objects // until we encounter the end of the sequence. //report("INTO FOR LOOP.\n"); for (var ndx = index + 2 ; ndx < numberOfExpressionObjects ; ++ndx) { var objectCurrentInSequence = expressionObjects[ndx]; //report("Next object in sequence = " + objectCurrentInSequence.className + " " + objectCurrentInSequence.value + "\n"); objectToItsRight = null; if (ndx < numberOfExpressionObjects - 1) { objectToItsRight = expressionObjects[ndx + 1]; //report("objectToItsRight = " + objectToItsRight + "\n"); } // otherwise we are at the very last object in the array so there's nothing to its right // and we're just left with objectToItsRight = null, which is OK. if (Algebra.isNonOperator(objectCurrentInSequence)) { // We hit a non-operator. So we're done gathering up the sequence of adds or subtracts. //report("objectCurrentInSequence is a Non-Operator, so exiting from loop.\n"); indexOfLastAddOrSubtractObject = ndx - 1; break; } else { // We hit an operator type of object. //report("We have an operator as the object.\n"); if ((objectCurrentInSequence instanceof Algebra.OperatorPlus) || (objectCurrentInSequence instanceof Algebra.OperatorMinus)) { //report("Another Add or Subtract.\n"); // Then this is ANOTHER add or subtract object, so save it for our sequence of objects. // Of course, this also needs a non-operator to its right in order to be a valid // add or subtract. if ((objectToItsRight != null) && Algebra.isNonOperator(objectToItsRight)) { //report("which means another valid add or subtract\n"); // Then yes indeed we have another valid add or subtract. //report(">> PUSHING >>\n" + objectCurrentInSequence + "\n"); sequenceOfAddSubtractObjects.push(objectCurrentInSequence); // Which is the explicit Operator Multiply. //report(">> PUSHING >>\n" + objectToItsRight + "\n"); sequenceOfAddSubtractObjects.push(objectToItsRight); // Point at the next object to the right of the object which is to the right of the multiply object. ++ndx; indexOfLastAddOrSubtractObject = ndx + 1; } else { // The object to the right of the add or subtract operator is an operator. // We appear to have hit a dangling add or subtract at the end of the expression. adviseUserExpressionError("Misplaced adition or subtraction operator encountered."); return null; // NOTE WARNING. Dang, how do we handle something like A * B * -2 ???? // MAYBE specifically look for the minus sign, and maybe also the plus sign? } } else { // We hit an operator that is NOT an add or subtract. //report("It is an operator but NOT an Operator Multiply\nExiting from gathering loop."); indexOfLastAddOrSubtractObject = ndx - 1; // So we're done looking for sequential explicit and implicit multiplied objects. break; } } } //report("OUT OF LOOP\n"); // Replace the sequence of objects with an Algebra.GroupSequenceMultiply object //report("Replace the sequence of objects with an Algebra.GroupSequenceMultiply object.\n"); expressionObjectsGroupified.push(new Algebra.GroupSequenceAddSubtract(objectCurrent.parentObject, parentObjectId, sequenceOfAddSubtractObjects)); index = indexOfLastAddOrSubtractObject; // This will get indexed PAST the one we just handled // at the next "for" loop, above. } else { //report("Not an add or subtract. Just push to expressionObjectsGroupified\n"); // The currentObject is for neither an add or subtract.. // So just push it into our expressionObjectsGroupified. expressionObjectsGroupified.push(objectCurrent); } } //report("EXITING FROM Algebra.groupifySequenceObjects(expressionObjects) with " + expressionObjectsGroupified.length + "objects\n"); //alert("EXITING FROM Algebra.groupifySequenceObjects(expressionObjects) with " + expressionObjectsGroupified.length + "objects\n"); return expressionObjectsGroupified; } Algebra.isNonOperator = function(object) { // This returns true if the specified object is not one of the elementary operators, // which includes +, -, *, / NOTE Perhaps include SQRT, pi, e, etc. // WARNING NOT FINISHED !!!! if (object instanceof Algebra.OperatorPlus) return false; else if (object instanceof Algebra.OperatorMinus) return false; else if (object instanceof Algebra.OperatorMultiply) return false; else if (object instanceof Algebra.OperatorMultiplyImplicit) return false; else if (object instanceof Algebra.OperatorDivide) return false; return true; } /////////////////////////////////////////////////////////////////// // // ALGEBRA EQUATION TRAVERSING // /////////////////////////////////////////////////////////////////// Algebra.getGroupFractionParent = function(object) { // Given the object this traverses UP the hierarchy and retuns the first // GroupFraction object it encounters, or null if no GroupFraction is encountered. if (!object) { reportNullObject("Algebra.getGroupFractionParent(object)","object"); return null; } if (!object.parentObjectId || (object.parentObjectId == null) || (object.parentObjectId.length == 0)) { //reportNullObject("Algebra.getGroupFractionParent(object)","object.parentObjectId"); return null; } //object = Algebra.getObjectById(g_myAlgebraObject.equation, object.parentObjectId); object = object.parentObject; if (!object) { return null; } if (object instanceof Algebra.GroupFraction) { return object; } // Recurse up the hierarchy. return Algebra.getGroupFractionParent(object); } Algebra.getGroupDenominatorParent = function(object) { // Given the object this traverses UP the hierarchy and retuns the first // GroupDenominator object it encounters, or null if no GroupDenominator is encountered. if (!object) { reportNullObject("Algebra.getGroupDenominatorParent(object)","object"); return null; } if (!object.parentObjectId || (object.parentObjectId == null) || (object.parentObjectId.length == 0)) { //reportNullObject("Algebra.getGroupDenominatorParent(object)","object.parentObjectId"); return null; } //object = Algebra.getObjectById(g_myAlgebraObject.equation, object.parentObjectId); object = object.parentObject; if (!object) { return null; } if (object instanceof Algebra.GroupDenominator) { return object; } // Recurse up the hierarchy. return Algebra.getGroupDenominatorParent(object); } Algebra.getGroupNumeratorParent = function(object) { // Given the object this traverses UP the hierarchy and retuns the first // GroupNumerator object it encounters, or null if no GroupNumerator is encountered. if (!object) { reportNullObject("Algebra.getGroupNumeratorParent(object)","object"); return null; } if (!object.parentObjectId || (object.parentObjectId == null) || (object.parentObjectId.length == 0)) { //reportNullObject("Algebra.getGroupNumeratorParent(object)","object.parentObjectId"); return null; } //object = Algebra.getObjectById(g_myAlgebraObject.equation, object.parentObjectId); object = object.parentObject; if (!object) { return null; } if (object instanceof Algebra.GroupNumerator) { return object; } // Recurse up the hierarchy. return Algebra.getGroupNumeratorParent(object); } Algebra.getGroupExpressionParent = function(object) { //report("IN Algebra.getGroupExpressionParent(object)\n"); //reportTabIn(); // This returns Algbra.GroupExpressionLeft or Algbra.GroupExpressionLeft object which is the parent of the // specified object, or null if an error. if (!object || (object == null)) { reportNullObject("Algebra.getGroupExpressionParent(object)","object"); //reportTabOut(); return null; } //report("object = "+object.iD+"\n"); if (!object.parentObjectId || (!object.parentObjectId) || (!object.parentObjectId.length) || (object.parentObjectId.length == 0)) { reportNullObject("Algebra.getGroupExpressionParent(object)","object.parentObjectId"); //reportTabOut(); return null; } //var parentObject = Algebra.getObjectById(g_myAlgebraObject.equation, object.parentObjectId); var parentObject = object.parentObject; if (!parentObject || (parentObject == null)) { //reportTabOut(); reportNullObject("Algebra.getGroupExpressionParent(object)","parentObject"); reportProgrammingError("Algebra.getGroupExpressionParent(object)","Was looking for parent = "+object.parentObjectId) return null; } if ((parentObject instanceof Algebra.GroupExpressionLeft) || (parentObject instanceof Algebra.GroupExpressionRight)) { //report("IN Algebra.getGroupExpressionParent() returning "+parentObject.className+"\n"); //reportTabOut(); return parentObject; } // othwerwise recurse up the hierarchy. //report("Recurse UP the hierarchy\n"); var groupExpressionParent = Algebra.getGroupExpressionParent(parentObject); //report("Returning groupExpressionParent: "+groupExpressionParent.iD+"\n"); //reportTabOut(); return groupExpressionParent; } Algebra.isDraggingEntireSideOfEquation = function() { //report("IN Algebra.isDraggingEntireSideOfEquation()\n"); //reportTabIn(); if (g_mySelectedObjects.length == 0) { reportProgrammingError("Algebra.isDraggingEntireSideOfEquation()","Called this function when g_mySelectedObjects is empty."); //reportTabOut(); return false; } var object = g_mySelectedObjects.getObject(0); if (!object || (object == null)) { reportNullObject("Algebra.isDraggingEntireSideOfEquation()","g_mySelectedObjects.getObject(0)"); //reportTabOut(); return false; } //report("Retrieved object from g_mySelectedObjects.getObject(0)\n"); //report("object is "+object.className+" : "+object.iD+"\n"); var expressionParent = Algebra.getGroupExpressionParent(object); if (!expressionParent || (expressionParent == null)) { reportProgrammingError("Algebra.isDraggingEntireSideOfEquation()","Failed to get expression parent."); //reportTabOut(); return false; } //report("expressionParent = "+expressionParent.className+"\n"); var lowestSequentialNonElementaryGroup = Algebra.getFirstGroupWithContentsDownTheHierarchy(expressionParent); //report("lowestSequentialNonElementaryGroup = "+lowestSequentialNonElementaryGroup.iD+"\n"); var isGroupContentsAllSelected = Algebra.isGroupContentsAllSelected(lowestSequentialNonElementaryGroup); //report("EXITING Algebra.isDraggingEntireSideOfEquation()\n"); //report("returning isGroupContentsAllSelected = "+(isGroupContentsAllSelected?"TRUE":"FALSE")+"\n"); //reportTabOut(); return isGroupContentsAllSelected; } Algebra.isDragObjectATopLevelObject = function() { //report("IN Algebra.isDragObjectATopLevelObject()\n"); //reportTabIn(); if (g_mySelectedObjects.length == 0) { reportProgrammingError("Algebra.isDragObjectATopLevelObject()","Called this when g_mySelectedObjects is empty."); //reportTabOut(); return false; } //g_mySelectedObjects.report(); var oneOfTheSelectedObjects = g_mySelectedObjects.getObject(0); if (!oneOfTheSelectedObjects || (oneOfTheSelectedObjects == null)) { reportNullObject("Algebra.isDragObjectATopLevelObject()","g_mySelectedObjects.getObject(0)"); //reportTabOut(); return false; } //report("oneOfTheSelectedObjects is "+oneOfTheSelectedObjects.className+" : "+oneOfTheSelectedObjects.iD+"\n"); var expressionParent = Algebra.getGroupExpressionParent(oneOfTheSelectedObjects); // Should return a GroupExpressionRight or GroupExpressionLeft object. if (!expressionParent || (expressionParent == null)) { reportProgrammingError("Algebra.isDragObjectATopLevelObject()","Failed to get expression parent."); //reportTabOut(); return false; } //report("BACK IN Algebra.isDragObjectATopLevelObject()expressionParent = "+expressionParent.className+"\n"); //alert("IN Algebra.isDragObjectATopLevelObject about to go to Algebra.isTopObject\n"); var isTopObject = Algebra.isTopObject(expressionParent, oneOfTheSelectedObjects); //report("BACK IN Algebra.isDragObjectATopLevelObject()\n"); //report("Returning isTopObject = "+(isTopObject?"TRUE":"FALSE")+"\n"); //reportTabOut(); return isTopObject; } Algebra.isChildOf = function(group, object) { //report("IN Algebra.isChildOf(group, object)\n"); // Returns true if the specified object is a child of the specified group. if (!group || (group == null)) { reportNullObject("Algebra.isChildOf(group, object)","group"); return false; } if (!object || (object == null)) { reportNullObject("Algebra.isChildOf(group, object)","object"); return false; } if (!group.isGroupTypeObject || (group.isGroupTypeObject == false)) { reportProgrammingError("Algebra.isChildOf(group, object)","Specified group is actually not a group."); return false; } if (!group.objects) { reportNullObject("Algebra.isChildOf(group, object)","group.objects"); return false; } var objects = group.objects; var numberOfObjects = objects.length; //report("Checking "+numberOfObjects+" child objects against "+object.iD+".\n"); for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = objects[index]; //report("thisObject = "+thisObject.className + " : " + thisObject.iD + "\n"); if (thisObject.iD == object.iD) { //report("Match! returning true.\n"); return true; } } return false; } Algebra.isObjectSoleObjectOnSide = function(object) { // This traverses UP the object hierarchy and determines whether the specified // object is the ONLY object. //report("IN Algebra.isObjectSoleObjectOnSide(object)\n"); //reportTabIn(); if (!object || (object == null)) { reportNullObject("Algebra.isObjectSoleObjectOnSide(object)","object"); //report("Returning FALSE\n"); //reportTabOut(); return false; } //report("object = "+object.iD+"\n"); if ((object instanceof Algebra.GroupExpressionLeft) || (object instanceof Algebra.GroupExpressionRight)) { //report("Hit the Group Expression (left or right)\n"); //report("Returning TRUE\n"); //reportTabOut(); return true; } if (!object.parentObjectId || (object.parentObjectId == null) || !object.parentObjectId.length) { reportNullObject("Algebra.isObjectSoleObjectOnSide(object)","object.parentObjectId"); //report("Returning FALSE\n"); //reportTabOut(); return false; } //var objectParent = Algebra.getObjectById(g_myAlgebraObject.equation, object.parentObjectId); var objectParent = object.parentObject; if (!objectParent || (objectParent == null)) { reportNullObject("Algebra.isObjectSoleObjectOnSide(object)","objectParent"); //report("Returning FALSE\n"); //reportTabOut(); return false; } if (!objectParent.objects || (objectParent.objects == null) || !objectParent.objects.length) { reportProgrammingError("Algebra.isObjectSoleObjectOnSide(object)","object's parent has no children."); //report("Returning FALSE\n"); //reportTabOut(); return false; } if (Algebra.getNumberOfAlgebraChildren(objectParent) > 1) { //report("Object's parent has multiple children. Returning FALSE\n"); //reportTabOut(); return false; } //report("Object's parent has only one child. Recursing UP the hierarchy.\n"); var isObjectSoleObjectOnSide = Algebra.isObjectSoleObjectOnSide(objectParent); //report("Returning "+(isObjectSoleObjectOnSide?"TRUE":"FALSE")+"\n"); //reportTabOut(); return isObjectSoleObjectOnSide; } Algebra.areAllFractionPartChildrenSelected = function(object) { //report("IN Algebra.areAllFractionPartChildrenSelected(object)\n"); // Given an object that is presumably a child of a fraction part (either // numerator or denominator) this returns true if ALL of the immediate // children of the fraction part are selected. var numeratorOrDenominatorParent = Algebra.getNumeratorOrDenominatorParent(object); if (!numeratorOrDenominatorParent || (numeratorOrDenominatorParent == null)) { reportNullObject("Algebra.areAllFractionPartChildrenSelected(object)","numeratorOrDenominatorParent"); return false; } var lowestSequentialNonElementaryGroup = Algebra.getFirstGroupWithContentsDownTheHierarchy(numeratorOrDenominatorParent); if (!lowestSequentialNonElementaryGroup || (lowestSequentialNonElementaryGroup == null)) { reportNullObject("Algebra.areAllFractionPartChildrenSelected(object)","lowestSequentialNonElementaryGroup"); return false; } var objects = lowestSequentialNonElementaryGroup.objects; for (index = 0 ; index < objects.length ; ++index) { var childObject = objects[index]; if (!Algebra.isDropBar(childObject)) { if (!childObject.currentDisplayState) { reportProgrammingError("Algebra.areAllFractionPartChildrenSelected(object)","childObject does not but should have currentDisplayState."); return false; } if (childObject.currentDisplayState != k_displayStateSelected) { // This child is not selected, so it it NOT true that all children are selected. return false; } } } // We got through all of the children and all of them are selected. return true; } Algebra.getNumeratorOrDenominatorParent = function(object) { // ONLY call this if you already know that the object is a child of a numerator // or denominator. // Given the object this traverses UP the hierarchy and returns either the // group numerator or group denominator parent. //report("IN Algebra.getNumeratorOrDenominatorParent(object)\n"); //reportTabIn(); if (!object) { reportNullObject("Algebra.getNumeratorOrDenominatorParent","object"); //report("Return from Algebra.getNumeratorOrDenominatorParent with NULL because parameter to function is NULL.\n"); //reportTabOut(); return null; } if (!object.parentObjectId || (object.parentObjectId == null) || (object.parentObjectId.length == 0)) { // When it gets to the very top there in fact IS no parent object. //report("Return from Algebra.getNumeratorOrDenominatorParent with NULL because hit TOP of hierarchy.\n"); //reportTabOut(); return null; } var parentObjectID = object.parentObjectId; //report("parentObjectID = " + parentObjectID + "\n"); //var parentObject = Algebra.getObjectById(g_myAlgebraObject.equation, parentObjectID); var parentObject = object.parentObject; if (!parentObject || (parentObject == null)) { reportNullObject("Algebra.getNumeratorOrDenominatorParent", "parentObject"); //report("Return from Algebra.isPartOfNumerator with NULL because parentObject not found by Algebra.getObjectById.\n"); return null; } //report("parentObject = "+parentObject.className+"\n"); if (parentObject instanceof Algebra.GroupNumerator) { //report("returning with group parent " + parentObject.className + " = " + parentObject.iD + "\n"); //reportTabOut(); return parentObject; } if (parentObject instanceof Algebra.GroupDenominator) { //report("returning with group parent " + parentObject.className + " = " + parentObject.iD + "\n"); //reportTabOut(); return parentObject; } // otherwise, recurse up the hierarchy. //report("Recursing up the hierarchy to getNumeratorOrDenominatorParent(parentObject)\n"); //reportTabOut(); return Algebra.getNumeratorOrDenominatorParent(parentObject); } Algebra.getObjectById = function(startingObject, idToFind) { //report("IN Algebra.getObjectById( startAt= " + startingObject.iD+ " to find= "+idToFind + ")\n"); //reportTabIn(); // Note that this is a recursively called function. if (!startingObject) { reportNullObject("Algebra.getObjectById(startingObject, idToFind)","startingObject"); //reportTabOut(); return null; } if (!idToFind || idToFind.length == 0) { reportNullObject("Algebra.getObjectById(startingObject, idToFind)","idToFind [possibly forgot to specify the startingObject ? ]"); //reportTabOut(); return null; } //report("idToFind = "+idToFind+"\n"); if (startingObject.iD && (startingObject.iD == idToFind)) { // We found the object that we are looking for. It was the startingObject itself. //report(">> FOUND IT << returning "+startingObject.iD +"\n"); //report("returning from startingObject.findObjectById()\n"); //reportTabOut(); return startingObject; } if (startingObject instanceof Array) //NOTE WARNING WHAT THE HELL?? Why am I testing for Array??? The StartingObject is an OBJECT!!! // It may CONTAIN an array, but it is not itself an array!! { //reportTabIn(); for (var index = 0 ; index < startingObject.length ; ++index) { var object = Algebra.getObjectById(startingObject[index], idToFind); if (object) { //report("Returning "+object.iD+"\n"); //reportTabOut(); return object; } } //reportTabOut(); //report("FAILED to find "+idToFind+"\n"); //reportTabOut(); return null; } //report("This object class = " + startingObject.className + " ID = " + startingObject.iD + "\n"); // The startingObject is not the object that were looking for. if (startingObject.objects) { // but the startingObject has a set of sub-objects, // so look through those. var subobjects = startingObject.objects; //report(startingObject.iD+" has " + subobjects.length + " to search.\n"); //report("startingObject HTML = \n"+startingObject.toHTML(0,0,k_displayStateNormal)+"\n"); //report("NOW looking through its "+subobjects.length+" objects\n"); //reportTabIn(); for (var index = 0 ; index < subobjects.length ; ++index) { var subobject = subobjects[index]; var returnedObject; returnedObject = Algebra.getObjectById(subobject, idToFind); if (returnedObject) { //report("we found what we are looking for.\n"); // we found what we are looking for. //report("returning from Algebra.getObjectById()\n"); //reportTabOut(); //reportTabOut(); return returnedObject; } } //reportTabOut(); } else { //report("startingObject, "+startingObject.className+" is not a group, has no objects to search\n"); } // We still haven't found the object we are looking for but the // startingObject that we are considering may have its own findObjectById, // which SHOULD look only at its non-array objects, such as the // Group Parentheses, which has an object array but also has two Parentheses objects. //report("Not found in this object's array of objects (or it doesn't have an array of objects).\n"); if (startingObject.findObjectById) { //report("But this objects has a findObjectByID method, so try calling that.\n"); var returnedObject; returnedObject = startingObject.findObjectById(idToFind); //report("returning from startingObject.findObjectById()\n"); //reportTabOut(); return returnedObject; } //report("And the startingObject does not have its own findObjectById method.\n"); //report("returning from startingObject.findObjectById()\n"); //reportTabOut(); return null; } Algebra.removeLeadingPlusSigns = function(objects) { // WARNING NOTE: This does NOT take into account any possible drop bars. Should it? if (!objects || !objects.length) { reportNullObject("Algebra.removeLeadingPlusSigns(objects)","objects"); return; } // Remove any leading (extraneous) plus signs. while (true) { if (objects[0] instanceof Algebra.OperatorPlus) { // Remove that OperatorPlus objects.shift(); // Which removes the first item from the array. } else { break; } } } Algebra.removeTrailingPlusAndMinusSigns = function(objects) { // WARNING NOTE: This does NOT take into account any possible drop bars. Should it? // Remove any trailing (extraneous) plus or minus signs. if (!objects || !objects.length) { reportNullObject("Algebra.removeTrailingPlusAndMinusSigns(objects)","objects"); return; } while (true) { if ((objects[objects.length-1] instanceof Algebra.OperatorPlus) || (objects[objects.length-1] instanceof Algebra.OperatorMinus)) { // Remove that OperatorPlus or OperatorMinus objects.pop(); // Which removes the last item from the array. } else { break; } } } Algebra.removeLeadingMultiplicationSigns = function(objects) { report("IN Algebra.removeLeadingMultiplicationSigns(objects)\n"); reportTabIn(); // WARNING NOTE: This does NOT take into account any possible drop bars. Should it? if (!objects || !objects.length) { reportNullObject("Algebra.removeLeadingMultiplicationSigns(objects)","objects"); reportTabOut(); return; } Algebra.showObjectArray("BEFORE",objects); // Remove any leading (extraneous) multiplication signs. var numberOfObjects = objects.length; for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = objects[index]; if (!Algebra.isDropBar(thisObject)) { if (Algebra.isOperatorMultiply(thisObject)) { // Get rid of that leading, irrelevant multiply operator. objects.splice(index, 1); --numberOfObjects; } else { // We've encountered a non-dropbar, non-multiply, so get out. break; } } else { // It's a drop bar, so ignore it. } } Algebra.showObjectArray("AFTER",objects); } Algebra.assureExplicitMultiplyBetweenConsecutiveNumbers = function(objects) { if (!objects || !objects.length) { reportNullObject("Agebra.assureExplicitMultiplyBetweenConsecutiveNumbers","objects"); return; } // For each implicit multiplication operator in the array of objects, check to see if // it is surrounded by two Integer objects, and if it is, substitute an EXCPLICIT // multiplicaiton operator. var numberOfObjects = objects.length; for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = objects[index]; if (thisObject instanceof Algebra.OperatorMultiplyImplicit) { if ((objects[index-1] instanceof Algebra.Integer) && (objects[index+1] instanceof Algebra.Integer)) { // Yes, it s surrounded Integer objects. // So substitute an OperatorMultiply for the OperatorMultiplyImplicit. objects[index] = new Algebra.OperatorMultiply(thisObject.parentObject, thisObject.parentObjectId); } } } } Algebra.removeIrrelevantParentheses = function(startingObject) { //report("IN Algebra.removeIrrelevantParentheses(startingObject)\n"); //reportTabIn(); if (!startingObject || (startingObject == null)) { reportNullObject("Algebra.removeIrrelevantParentheses(startingObject)","startingObject"); //reportTabOut(); return; } //report("startingObject = "+startingObject.className+"\n"); if (Algebra.isElementaryObject(startingObject)) { //report("Is Elementary object. Returning\n"); //reportTabOut(); return; } else if (startingObject instanceof Algebra.GroupParentheses) { //report("Is Parentheses.\n"); var numberOfNonDropBarChildren = Algebra.getNumberOfNonDropBarChildren(startingObject); //report("numberOfNonDropBarChildren = "+numberOfNonDropBarChildren+"\n"); if (numberOfNonDropBarChildren == 1) { //report("The parentheses encloses just ONE object.\n"); // The parentheses encloses just ONE object. var enclosedObject = Algebra.getFirstNonDropBarObjectInArray(startingObject.objects); //report("Removing the parentheses\n"); Algebra.removeParentheses(startingObject); //report("Recurse down that (no longer) enclosed object.\n"); // Recurse down that (no longer) enclosed object. Algebra.removeIrrelevantParentheses(enclosedObject); //report("Returning from Algebra.removeIrrelevantParentheses("+startingObject.iD+")\n"); //reportTabOut(); return; } } //report("startingObject is neither an elementary object nor a Parentheses object.\n"); //report("Recurse down all children objects.\n"); // Recurse down all children objects. var objects = startingObject.objects; var numberOfObjects = objects.length; for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = objects[index]; if (!Algebra.isDropBar(thisObject) && !Algebra.isOperator(thisObject)) { Algebra.removeIrrelevantParentheses(thisObject); } } //report("Returning from Algebra.removeIrrelevantParentheses("+startingObject.iD+")\n"); //reportTabOut(); } Algebra.removeParentheses = function(parenthesesObject) { // The input is a parenthesesObject. // IF that parenthesesObject has JUST ONE (non-drop bar) child object (e.g. Integer, Variable, Fraction, ...) // then the parenthesesObject effectively gets removed and the child object REPLACES the // parenthesesObject within the array of objects of the PARENT. // // NOTE that this does NOT recurse up or down the hierarchy. It only removes the SINGLE GroupParentheses object. if (!parenthesesObject || (parenthesesObject == null)) { reportNullObject("Algebra.removeParentheses(parenthesesObject)","parenthesesObject"); return; } if (!(parenthesesObject instanceof Algebra.GroupParentheses)) { reportProgrammingError("Algebra.removeParentheses(parenthesesObject)","parenthesesObject is not a GroupParentheses but should be."); return; } var numberOfNonDropBarChildren = Algebra.getNumberOfNonDropBarChildren(parenthesesObject); if (numberOfNonDropBarChildren != 1) { return; } var soleChildObject = Algebra.getFirstNonDropBarObjectInArray(parenthesesObject.objects); if (!soleChildObject || (soleChildObject == null)) { reportNullObject("Algebra.removeParentheses(parenthesesObject)","soleChildObject"); return; } var shouldRemoveParentheses = false; // ( ( A + B - X * Z ) ???????? if (Algebra.isElementaryObject(soleChildObject) || (soleChildObject instanceof Algebra.GroupFraction)) { shouldRemoveParentheses = true; } else if ((soleChildObject instanceof Algebra.GroupSequenceMultiply) || (soleChildObject instanceof Algebra.GroupSequenceAddSubtract)) { /*****???? I am totally baffled about this. I am not properly handling a number of cases where parentheses shouldbe removed but can't figure out how to do it. Sometimes it seems to be related to whether the parent object is an add/sub or mult. Others, ???? // Go down the hierarchy and // find the first sub object that we can attach. var theCurrentObject = soleChildObject; while true { var numberOfNonDropBarChildren = getNumberOfNonDropBarChildren(theCurrentObject); if (numberOfNonDropBarChildren == 1) { var childObject = Algebra.getNonDropBarObjectFromArray(theCurrentObject, 0); if (Algebra.isElementaryObject(childObject) || (childObject instanceof Algebra.GroupFraction)) { soleChildObject = childObject; shouldRemoveParentheses = true; break; } else if (childObject instanceof Algebra.GroupParentheses) { // Down one more level. theCurrentObject = childObject; } else if ((childObject instanceof Algebra.GroupSequenceMultiply) || (childObject instanceof Algebra.GroupSequenceAddSubtract)) { // Down one more level. theCurrentObject = childObject; } else { soleChildObject = childObject; shouldRemoveParentheses = true; break; } } else if (numberOfNonDropBarChildren == 2) { if (theCurrentObject instanceof Algebra.GroupSequenceAddSubtract) { } else????? } else { break; } } *******/ } else { //?? } if (shouldRemoveParentheses) { //var parentObject = Algebra.getObjectById(g_myAlgebraObject.equation, parenthesesObject.parentObjectId); var parentObject = parenthesesObject.parentObject; if (!parentObject || (parentObject == null)) { reportNullObject("Algebra.removeParentheses(parenthesesObject)","parentObject"); return; } // Go through the children of the parentObject and when we encounter the parenthesesObject within those // children, substitute the soleChildObject of the parenthesesObject. var objects = parentObject.objects; var numberOfObjects = objects.length; report("Going through "+numberOfObjects+" objects of the parentObject\n"); report("Looking for "+parenthesesObject.iD+"\n"); reportTabIn(); for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = objects[index]; report("Looking at "+thisObject.iD+"\n"); if (thisObject.iD == parenthesesObject.iD) { report("Found it!\n"); soleChildObject.parentObjectId = parentObject.iD; objects[index] = soleChildObject; reportTabOut(); return; } } reportTabOut(); reportProgrammingError("Algebra.removeParentheses(parenthesesObject)","Failed to find parenthesesObject in parentObject's array of objects."); } reportTabOut(); } Algebra.removeIrrelevantMultiplicationSigns = function(parentObject) { report("IN Algebra.removeIrrelevantMultiplicationSigns(parentObject)\n"); reportTabIn(); if (parentObject == null) { reportNullObject("Algebra.removeIrrelevantMultiplicationSigns(parentObject)","parentObject"); reportTabOut(); return false; } report("parentObject = "+parentObject.iD+"\n"); if (parentObject instanceof Algebra.GroupSequenceMultiply) { report("In MULTIPLY GROUP\n"); Algebra.removeLeadingMultiplicationSigns(parentObject.objects); } if (parentObject.objects && parentObject.objects.length) { var objects = parentObject.objects; var numberOfObjects = objects.length; report("CHECKING "+numberOfObjects+" objects\n"); reportTabIn(); for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = objects[index]; Algebra.removeIrrelevantMultiplicationSigns(thisObject); } reportTabOut(); } reportTabOut(); } var g_DEPTH_removeIrrelevantGroupings = 0; Algebra.removeIrrelevantGroupings = function(parentObject) { report("IN Algebra.removeIrrelevantGroupings("+parentObject.iD+")\n"); reportTabIn(); ++g_DEPTH_removeIrrelevantGroupings; report("Depth = "+g_DEPTH_removeIrrelevantGroupings+"\n"); if (g_DEPTH_removeIrrelevantGroupings > 10) { report("ERROR Too deep ("+g_DEPTH_removeIrrelevantGroupings+") within Algebra.removeIrrelevantGroupings\n"); reportTabOut(); alert("Algebra.removeIrrelevantGroupings\nDEPTH EXCEEDED"); g_DEPTH_removeIrrelevantGroupings = 0; return; } if (!parentObject || (parentObject == null)) { reportNullObject("Algebra.removeIrrelevantGroupings(parentObject)","parentObject"); reportTabOut(); --g_DEPTH_removeIrrelevantGroupings; return; } report("parentObject = "+parentObject.className+" : "+parentObject.iD+"\n"); if (Algebra.isElementaryObject(parentObject)) { report("EXITING because the parentObject is an elementary object.\n"); // Even though an elementary object (Integer, Variable, or Constant) has children // those children cannot have children themselevs, so there is no possibility // of removing an irrelevant grouping. Or, to put it another way, this is a perfectly // valid grouping. // So, we're done with it. reportTabOut(); --g_DEPTH_removeIrrelevantGroupings; return; } if (!parentObject.objects || (parentObject.objects == null)) { report("EXITING because the parentObject has no children.\n"); // The parentObject, whatever it is, has no children, so there is no possibility // of removing an irrelevant grouping. // So we're done with it. reportTabOut(); --g_DEPTH_removeIrrelevantGroupings; return; } var numberOfNonDropBarChildren = Algebra.getNumberOfNonDropBarChildren(parentObject); report("numberOfNonDropBarChildren = "+numberOfNonDropBarChildren+"\n"); if (numberOfNonDropBarChildren <= 0) { reportProgrammingError("Algebra.removeIrrelevantGroupings(parentObject)","numberOfNonDropBarChildren for object is ZERO."); reportTabOut(); --g_DEPTH_removeIrrelevantGroupings; return; } if (numberOfNonDropBarChildren > 1) { report("This parentObject has more than one non-drop-bar children, which means that\n"); report("this parentObject has no possibility of removing an irrelevant grouping.\n"); report("Yet it's still possible for its children to have irrelevant groupings, so\n"); report("recurse DOWN the hierarchy for each child.\n"); // This object has more than one non-drop-bar children, which means that // this object has no possibility of removing an irrelevant grouping. // Yet it's still possible for its children to have irrelevant groupings, so // recurse DOWN the hierarchy for each child. // SPECIAL CASE !!!! /// WARNING: BUT WHY? WHy isn't this properly handled by the -else- section?? if (parentObject instanceof Algebra.GroupFraction) { report("Object is a Fraction: Special case\n"); // Recurse down for the object BELOW the numerator and BELOW the denominator. var objectBelowNumerator = Algebra.getFirstNonDropBarObjectInArray(parentObject.objectNumerator.objects); // NOTE it is POSSIBLE for either the Numerator or the Denominator to be empty of objects. THia is due to the // fact that the dropDragItemAtDropTarget function may leave it empty (for subsequent filling AFTER returning // from the dropDragItemAtDropTarget), yet it also calls this removeIrrelevantGroupings function. if (objectBelowNumerator && (objectBelowNumerator != null)) { Algebra.removeIrrelevantGroupings(objectBelowNumerator); } var objectBelowDenominator = Algebra.getFirstNonDropBarObjectInArray(parentObject.objectDenominator.objects); if (objectBelowDenominator && (objectBelowDenominator != null)) { Algebra.removeIrrelevantGroupings(objectBelowDenominator); } } else { var objects = parentObject.objects; var numberOfObjects = objects.length; reportTabIn(); for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = objects[index]; if (!thisObject || (thisObject == null)) { reportNullObject("Algebra.removeIrrelevantGroupings(parentObject)","thisObject"); reportTabOut(); reportTabOut(); --g_DEPTH_removeIrrelevantGroupings; return; } if (!Algebra.isDropBar(thisObject)) // Actually may not need this test. { report("Recursing DOWN to child # "+index+" of object.\n"); Algebra.removeIrrelevantGroupings(thisObject); } } reportTabOut(); } reportTabOut(); --g_DEPTH_removeIrrelevantGroupings; return; } report("The parentObject has just one child.\n"); // The parentObject has just one child. var singleChildOfObject = Algebra.getFirstNonDropBarObjectInArray(parentObject.objects); report("singleChildOfObject = "+singleChildOfObject.iD+"\n"); if (!singleChildOfObject.objects || (singleChildOfObject.objects == null)) { report("The singleChildOfObject has NO children itself, so nothing to remove.\n"); // The singleChildOfObject has NO children itself, so nothing to remove. reportTabOut(); --g_DEPTH_removeIrrelevantGroupings; return; } if (Algebra.isElementaryObject(singleChildOfObject)) { // It's an Integer or Variable. report("The singleChildOfObject is an ELEMENTARY object (Integer or Variable), so nothing to remove.\n"); // The singleChildOfObject is an ELEMENTARY object, so nothing to remove. reportTabOut(); --g_DEPTH_removeIrrelevantGroupings; return; } var numberOfChildrenOfTheSingleChild = Algebra.getNumberOfNonDropBarChildren(singleChildOfObject); report("numberOfChildrenOfTheSingleChild = "+numberOfChildrenOfTheSingleChild+"\n"); var needToRemoveIrrelevantGrouping = false; if (singleChildOfObject instanceof Algebra.GroupSequenceAddSubtract) { report("singleChildOfObject is an Algebra.GroupSequenceAddSubtract\n"); // Then it's possible that all it has is a unary minus type of content. // First make sure that it does not have any leading, extraneous plus signs. Algebra.removeLeadingPlusSigns(singleChildOfObject.objects); // Get numberOfChildrenOfTheSingleChild again, since we might have removed a plus sign. numberOfChildrenOfTheSingleChild = Algebra.getNumberOfNonDropBarChildren(singleChildOfObject); report("After removeLeadingPlusSigns, numberOfChildrenOfTheSingleChild = "+numberOfChildrenOfTheSingleChild+"\n"); if (numberOfChildrenOfTheSingleChild == 2) { report("Since num children is 2 Then we PROBABLY have a unary minus.\n"); // Then we PROBABLY have a unary minus. // In fact, it SHOULD BE a unary minus, since the only possible way that we can have // just TWO non-dropbar objects within a GroupSequenceAddSubtract is with an OperatorMinus // followed by a non-operator object. var firstNonDropBarObject = Algebra.getFirstNonDropBarObjectInArray(singleChildOfObject.objects) if (!firstNonDropBarObject || (firstNonDropBarObject == null)) { reportNullObject("Algebra.removeIrrelevantGroupings(parentObject)","firstNonDropBarObject"); reportTabOut(); --g_DEPTH_removeIrrelevantGroupings; return; } if (!(firstNonDropBarObject instanceof Algebra.OperatorMinus)) { reportProgrammingError("Algebra.removeIrrelevantGroupings(parentObject)","firstNonDropBarObject within the GroupSequenceAddSubtract that has just TWO non-dropbar children is NOT an OperatorMinus. That should not be possible."); reportTabOut(); --g_DEPTH_removeIrrelevantGroupings; return; } else { // It's a unary minus, so we want to keep the GroupSequenceAddSubtract report("Is a UNARY MINUS. so we want to keep the GroupSequenceAddSubtract\n"); } } else if (numberOfChildrenOfTheSingleChild == 1) { report("Only one child of GroupSequenceAddSubtract.\n"); var firstNonDropBarObject = Algebra.getFirstNonDropBarObjectInArray(singleChildOfObject.objects) if (!firstNonDropBarObject || (firstNonDropBarObject == null)) { reportNullObject("Algebra.removeIrrelevantGroupings(parentObject)","firstNonDropBarObject"); reportTabOut(); --g_DEPTH_removeIrrelevantGroupings; return; } if (Algebra.isElementaryObject(firstNonDropBarObject)|| (firstNonDropBarObject instanceof Algebra.GroupFraction)) { // then it's an elementary object or a GroupFraction, so we want to keep the GroupSequenceAddSubtract. report("Is an elementary object or a GroupFraction, so we want to keep the GroupSequenceAddSubtract.\n") } else { report("Otherwise the single child of the GroupSequenceAddSubtract is not an elementary object\n"); report("so it looks like we can remove the GroupSequenceAddSubtract.\n"); // Otherwise the single child of the GroupSequenceAddSubtract is not an elementary object // so it looks like we can remove the GroupSequenceAddSubtract. needToRemoveIrrelevantGrouping = true; } } else { // otherwise the numberOfChildrenOfTheSingleChild is greater than 2, so report("The GroupSequenceAddSubtract has multiple children, so cannot remove it.\n"); } } else if (singleChildOfObject instanceof Algebra.GroupSequenceMultiply) { report("singleChildOfObject is GroupSequenceMultiply.\n"); Algebra.removeLeadingMultiplicationSigns(singleChildOfObject.objects); // Get numberOfChildrenOfTheSingleChild again, since we might have removed a multiplcation sign. numberOfChildrenOfTheSingleChild = Algebra.getNumberOfNonDropBarChildren(singleChildOfObject); report("After removeLeadingMultiplicationSigns\n"); report("numberOfChildrenOfTheSingleChild = "+numberOfChildrenOfTheSingleChild+"\n"); if (numberOfChildrenOfTheSingleChild == 1) { report("Only ONE child of GroupSequenceMultiply.\n"); var firstNonDropBarObject = Algebra.getFirstNonDropBarObjectInArray(singleChildOfObject.objects) if (Algebra.isElementaryObject(firstNonDropBarObject)) { // then it's an elementary object, so we want to keep the GroupSequenceMultiply. report("Is an elementary object. so we want to keep the GroupSequenceMultiply\n"); reportTabOut(); --g_DEPTH_removeIrrelevantGroupings; return; } // Otherwise it's not an elementary object so it looks like we can remove the GroupSequenceMultiply. report("Otherwise it's not an elementary object so it looks like we can remove the GroupSequenceMultiply.\n"); needToRemoveIrrelevantGrouping = true; } } else if (singleChildOfObject instanceof Algebra.GroupParentheses) { report("singleChildOfObject is GroupParentheses.\n"); // if (numberOfChildrenOfTheSingleChild == 1) // { // report("Only ONE child of GroupParentheses.\n"); // var firstNonDropBarObject = Algebra.getFirstNonDropBarObjectInArray(singleChildOfObject.objects) // if (Algebra.isElementaryObject(firstNonDropBarObject)) // { // // then it's an elementary object, so we want to keep the GroupParentheses. // report("Is an elementary object. so we want to keep the GroupParentheses\n"); // reportTabOut(); // --g_DEPTH_removeIrrelevantGroupings; // return; // } // // Otherwise it's not an elementary object so it looks like we can remove the GroupParentheses. // report("Otherwise it's not an elementary object so it looks like we can remove the GroupParentheses.\n"); needToRemoveIrrelevantGrouping = true; // } } // else if (singleChildOfObject instanceof Algebra.GroupFraction) // { // report("singleChildOfObject is GroupFraction.\n"); // needToRemoveIrrelevantGrouping = true; // } else { // The singleChildOfObject is neither a GroupSequenceAddSubtract nor a GroupSequenceMultiply // so the numberOfChildrenOfTheSingleChild should be sufficient to determine whether or not // we needToRemoveIrrelevantGrouping. report("singleChildOfObject is neither AddSub nor Multiply.\n"); if ((numberOfChildrenOfTheSingleChild == 1) && (!Algebra.isElementaryObject(singleChildOfObject))) { report("And numberOfChildrenOfTheSingleChild is ONE, AND it is NOT an elementary object.\n"); report("So setting needToRemoveIrrelevantGrouping = true\n"); needToRemoveIrrelevantGrouping = true; } } report("needToRemoveIrrelevantGrouping = "+(needToRemoveIrrelevantGrouping?"TRUE":"FALSE")+"\n"); if (!needToRemoveIrrelevantGrouping) { report("No irrelevant group at this point, recurse DOWN for next level.\n"); // So, just recurse DOWN the hierarchy for the singleChildOfObject. Algebra.removeIrrelevantGroupings(singleChildOfObject); } else { report("The original object has just ONE child and the singleChild of that original object ALSO has just ONE child, so we need to compress.\n"); // The original object has just ONE child and the singleChild of that original object ALSO has just ONE child, so we need to compress. report("Create an array of just that single child of the single child.\n"); // Create an array of just that single child of the single child. var singleChildOfSingleChild = Algebra.getFirstNonDropBarObjectInArray(singleChildOfObject.objects); report("singleChildOfSingleChild = "+singleChildOfSingleChild.iD+"\n"); if (!singleChildOfSingleChild || (singleChildOfSingleChild == null)) { reportNullObject("Algebra.removeIrrelevantGroupings(parentObject)","singleChildOfSingleChild"); reportTabOut(); --g_DEPTH_removeIrrelevantGroupings; return; } var array = new Array(singleChildOfSingleChild); // And attach that to the orignal object, effectively eliminating the intermediary, irrelevant object. report("Attach "+singleChildOfSingleChild.iD+" to "+parentObject.iD+"\n"); parentObject.setChildrenObjects(array); // And then Recurse AGAIN for the same parentObject, because it now has new children. report("Recurse AGAIN for the same parentObject = "+parentObject.iD+"\n"); report("because it now has new children.\n"); Algebra.removeIrrelevantGroupings(parentObject); } // A * B = ( 1 - 2 ) * X // Move *X to become denominator of A * B // Should enclose A * B within paren and remove paren from ( 1 - 2 ) // ( A * B ) / X = 1 - 2 report("EXITING Algebra.removeIrrelevantGroupings\n"); reportTabOut(); --g_DEPTH_removeIrrelevantGroupings; return; } Algebra.removeOuterParentheses = function(originalObject) { // Given the specified originalObject this effectively removes the outer parentheses from // the IMMEDIATE CHILD object. That is, if the originalObject's sole child is a Group Parentheses // then the children of the Group Parentheses become the children of the object. // If there are multiple Group Parentheses down the hierarchical path, then it finds the // last such Group Parentheses in its search down the path and uses its children to become // the children of the original object. // // For example: // // ( ( A + B ) ) // // would end up as: // // A + B // // But: // // ( ( A + B ) * X ) ) // // would end up as: // // (A + B ) * X //report("IN Algebra.removeOuterParentheses(originalObject)\n"); //reportTabIn(); //report("OriginalObject = \n"+originalObject+"\n"); if (!originalObject || (originalObject == null)) { reportNullObject("Algebra.removeOuterParentheses(object)", "object"); //reportTabOut(); return; } if (!originalObject.objects || !originalObject.objects.length) { report("originalObject has no children\n"); //reportTabOut(); return; } if (Algebra.getNumberOfAlgebraChildren(originalObject) > 1) { //report("originalObject has more than one algebra (non-drop-bar) children\n"); // There are more than one child to the original // object, so there CANNOT be enclosing parentheses. //reportTabOut(); return; } var parent = originalObject; // Find the first parent (down the child path) that either has multiple // children (in which case it CANNOT be enclosing parentheses) or // whose sole child is NOT a parentheses. //report("Entering loop\n"); //reportTabIn(); while (Algebra.getNumberOfAlgebraChildren(parent) == 1) { //report("parent is "+parent.className+"\n"); //report("parent has ONE child\n"); var soleChild = Algebra.getSoleAlgebraObjectInGroup(parent); if (!soleChild || (soleChild == null)) { //reportTabOut(); //reportTabOut(); return; } //report("soleChild is "+soleChild.iD+"\n"); if (!(soleChild instanceof Algebra.GroupParentheses)) { //report("SOLE child is NOT a GroupParentheses\n"); //report("setting "+soleChild.className+" as soleChild of originalObject\n\n"); var array = new Array(); array.push(soleChild); originalObject.setChildrenObjects(array); //report("OriginalObject NOW = \n"+originalObject+"\n"); //report("returning\n"); //reportTabOut(); //reportTabOut(); return; } //report("Setting parent = child\n"); parent = soleChild; } //reportTabOut(); //report("OUT OF LOOP. Must have hit non-GroupParentheses parent with multiple children.\n"); // Must have hit parent with multiple children. // Attach this parent object as the sole child of the originalObject. //report("pushing "+parent.className+" onto originalObject.objects\n\n"); var array = new Array(); array.push(parent); originalObject.setChildrenObjects(array); //report("OriginalObject NOW = \n"+originalObject+"\n"); //reportTabOut(); return; } Algebra.getFirstGroupWithContentsDownTheHierarchy = function(startingGroup) { // Starting at the specified group this looks DOWN the hierarchy and returns the // first Group object that it finds which does NOT simply have another Group object // as a child. report("IN Algebra.getFirstGroupWithContentsDownTheHierarchy(startingGroup)\n"); reportTabIn(); if (!startingGroup || (startingGroup ==null)) { reportNullObject("Algebra.getFirstGroupWithContentsDownTheHierarchy(startingGroup)","startingGroup"); reportTabOut(); return null; } report("startingGroup = "+startingGroup.iD+"\n"); if (!startingGroup.isGroupTypeObject || (startingGroup.isGroupTypeObject == false)) { reportProgrammingError("Algebra.getFirstGroupWithContentsDownTheHierarchy(startingGroup)","startingGroup is not a group"); reportTabOut(); return null; } if (Algebra.isElementaryObject(startingGroup)) { reportProgrammingError("Algebra.getFirstGroupWithContentsDownTheHierarchy(startingGroup)","startingGroup is an elementary object"); reportTabOut(); return null; } var groupObject = startingGroup; while (true) { numberOfAlgebraChildren = Algebra.getNumberOfAlgebraChildren(groupObject); report("numberOfAlgebraChildren = "+numberOfAlgebraChildren+"\n"); if (numberOfAlgebraChildren > 1) { report("Since numberOfAlgebraChildren > 1, returning groupObject = "+groupObject.iD+"\n"); reportTabOut(); return groupObject; } // It appears that there is only ONE Algebra child. var child = Algebra.getSoleAlgebraObjectInGroup(groupObject); if (!child.isGroupTypeObject || (child.isGroupTypeObject == false)) { // The sole child of the group is NOT itself a group. report("Since the sole child of the group is NOT itself a group, returning groupObject = "+groupObject.iD+"\n"); reportTabOut(); return groupObject; } // else the sole child of this group is itself a group. // However, the GroupFraction is a special case. if (child instanceof Algebra.GroupFraction) { report("Since the sole child of the group is a GroupFraction, returning groupObject = "+groupObject.iD+"\n"); reportTabOut(); return groupObject; } if (Algebra.isElementaryObject(child)) { report("Since the sole child of the group is an elementary object, returning groupObject = "+groupObject.iD+"\n"); reportTabOut(); return groupObject; } report("so go down one more level.\n"); // so go down one more level. groupObject = child; } } Algebra.getSoleFraction = function(startingObject) { // This looks for a sole fraction, starting at the startinObject. If it finds one then it returns that sole fraction. // Othwerise it returns null. // // This starts at the startingObject. // If the startingObject is itself null, then it returns null (and reports an error). // If the startingObject is itself a GroupFraction, then it returns that startingObject. // If the startingObject is itself an Elementary Object (i.e. Integer, Variable, Constant) then it returns null. // If the startingObject is itself an Operator Object (i.e. Plus, Minus, Multiply, MultiplyImplicit) then it returns null. // If the startingObject is neither a GroupFraction nor an Elementary Object (i.e. not an Integer, Variable, Constant) // but the startingObject has MORE THAN ONE non-dropbar sub-object then it returns null. // If the startingObject is neither a GroupFraction nor an Elementary Object (i.e. not an Integer, Variable, Constant) // and the startingObject has JUST ONE non-dropbar sub-object then it goes DOWN the hierarchy, recursively calling // this Algebra.getSoleFraction, passing that single sub-object. report("IN Algebra.getSoleFraction(startingObject)\n"); reportTabIn(); // If the startingObject is itself null, then it returns null (and reports an error). if (!startingObject || (startingObject == null)) { reportNullObject("Algebra.getSoleFraction(startingObject)","startingObject"); reportTabOut(); return null; } // If the startingObject is itself a GroupFraction, then it returns that startingObject. if (startingObject instanceof Algebra.GroupFraction) { report("staringObject is INDEED a GroupFraction, returning "+startingObject.iD+"\n"); reportTabOut(); return startingObject; } // If the startingObject is itself an Elementary Object (i.e. Integer, Variable, Constant) the it returns null. if (Algebra.isElementaryObject(startingObject)) { report("startingObject is an Elementaryobject, returning null.\n"); reportTabOut(); return null; } // If the startingObject is itself an Operator Object (i.e. Plus, Minus, Multiply, MultiplyImplicit) then it returns null. if (Algebra.isOperator(startingObject)) { report("startingObject is an Operator, returning null.\n"); reportTabOut(); return null; } // If the startingObject is neither a GroupFraction nor an Elementary Object (i.e. not an Integer, Variable, Constant) // but the startingObject has MORE THAN ONE non-dropbar sub-object then it returns null. var numberOfAlgebraSubObjects = Algebra.getNumberOfAlgebraChildren(startingObject); if (numberOfAlgebraSubObjects > 1) { report("startingObject has "+numberOfAlgebraSubObjects+" Algebra subObjects, returning null.\n"); reportTabOut(); return null; } // If the startingObject is neither a GroupFraction nor an Elementary Object (i.e. not an Integer, Variable, Constant) // and the startingObject has JUST ONE non-dropbar sub-object then it goes DOWN the hierarchy, recursively calling // this Algebra.getSoleFraction, passing that single sub-object. if (numberOfAlgebraSubObjects == 1) { report("startingObject has exactly ONE Algebra subObjects, doing recursive call.\n"); var soleSubObject = Algebra.getSoleAlgebraObjectInGroup(startingObject); var soleFraction = Algebra.getSoleFraction(soleSubObject); if (soleFraction != null) { report("Returning "+soleFraction.iD+"\n"); } else { report("returning null.\n"); } reportTabOut(); return soleFraction; } reportProgrammingError("Algebra.getSoleFraction(startingObject)","Encountered object ("+startingObject.iD+") with no Algebra sub-objects\n"); reportTabOut(); return null; } Algebra.isAlgebraObject = function(object) { // In this function, Algebra Object is defined as being one of the following: // // Digit // Integer // Letter // Variable // Function // GroupSequenceAddSubtract // GroupNumerator // GroupDenominator // GroupParentheses // GroupSequenceMultiply // GroupFraction // WARNING NOTE: If we add anything, such as CONSTANT ( e, gamma, etc. ) then those must be // added to this list. if (!object || (object == null)) { reportNullObject("Algebra.isAlgebraObject(object)","object"); return false; } if ((object instanceof Algebra.Digit) || (object instanceof Algebra.Integer) || (object instanceof Algebra.Letter) || (object instanceof Algebra.Variable) || (object instanceof Algebra.Function) || (object instanceof Algebra.GroupSequenceAddSubtract) || (object instanceof Algebra.GroupNumerator) || (object instanceof Algebra.GroupDenominator) || (object instanceof Algebra.GroupParentheses) || (object instanceof Algebra.GroupSequenceMultiply) || (object instanceof Algebra.GroupFraction) ) { return true; } return false; } Algebra.getFirstAlgebraObjectInGroup = function(groupObject) { //report("IN Algebra.getFirstAlgebraObjectInGroup(groupObject)("+groupObject.className+"\n"); if (!groupObject || (groupObject == null)) { reportNullObject("Algebra.getFirstAlgebraObjectInGroup(groupObject)","groupObject"); return null; } if (!groupObject.isGroupTypeObject || (groupObject.isGroupTypeObject == false)) { reportProgrammingError("Algebra.getFirstAlgebraObjectInGroup(groupObject)","groupObject NOT a group type object."); return null; } // Now go through the children of the groupObject and find the FIRST non-drop-bar object. var objects = groupObject.objects; var numberOfObjects = objects.length; //report("Looking through "+numberOfObjects+" objects looking for non-drop-bar\n"); for (var index = 0 ; index < numberOfObjects ; ++index) { var object = objects[index]; //report("Looking at "+object.iD+"\n"); if (!Algebra.isDropBar(object)) { // This is it!. //report("Found a non-drop-bar, returning it.\n"); return(object); } } //report("No algebra objects found, returning null\n"); return null; } Algebra.getLastAlgebraObjectInGroup = function(groupObject) { //report("IN Algebra.getLastAlgebraObjectInGroup(groupObject)("+groupObject.className+"\n"); if (!groupObject || (groupObject == null)) { reportNullObject("Algebra.getLastAlgebraObjectInGroup(groupObject)","groupObject"); return null; } if (!groupObject.isGroupTypeObject || (groupObject.isGroupTypeObject == false)) { reportProgrammingError("Algebra.getLastAlgebraObjectInGroup(groupObject)","groupObject NOT a group type object."); return null; } // Now go through the children of the groupObject and find the LAST non-drop-bar object. var objects = groupObject.objects; var numberOfObjects = objects.length; var lastAlgebraObject = null; //report("Looking through "+numberOfObjects+" objects looking for non-drop-bar\n"); for (var index = 0 ; index < numberOfObjects ; ++index) { var object = objects[index]; //report("Looking at "+object.iD+"\n"); if (!Algebra.isDropBar(object)) { // This is one. //report("Found a non-drop-bar, returning it.\n"); lastAlgebraObject = object; } } return lastAlgebraObject; } Algebra.getFirstNonDropBarObjectInArray = function(array) { //report("IN Algebra.getFirstNonDropBarObjectInArray(array)\n"); //reportTabIn(); if (!array || (array == null)) { reportNullObject("Algebra.getFirstNonDropBarObjectInArray(array)","array"); //reportTabOut(); return null; } // Now go through the objects of the array and find the FIRST non-drop-bar object. var numberOfObjects = array.length; //report("Looking through "+numberOfObjects+" objects looking for non-drop-bar\n"); //reportTabIn(); for (var index = 0 ; index < numberOfObjects ; ++index) { var object = array[index]; //report("Looking at "+object.iD+"\n"); if (!Algebra.isDropBar(object)) { // This is it!. //report("Found a non-drop-bar, returning it.\n"); //reportTabOut(); //reportTabOut(); return(object); } } //reportTabOut(); //report("No algebra objects found, returning null\n"); //reportTabOut(); return null; } Algebra.getLastNonDropBarObjectInArray = function(array) { //report("IN Algebra.getLastNonDropBarObjectInArray(array)\n"); if (!array || (array == null)) { reportNullObject("Algebra.getLastNonDropBarObjectInArray(array)","array"); return null; } // Now go through the children of the groupObject and find the LAST non-drop-bar object. var numberOfObjects = array.length; var lastAlgebraObject = null; //report("Looking through "+numberOfObjects+" objects looking for non-drop-bar\n"); for (var index = 0 ; index < numberOfObjects ; ++index) { var object = array[index]; //report("Looking at "+object.iD+"\n"); if (!Algebra.isDropBar(object)) { // This is one. //report("Found a non-drop-bar, returning it.\n"); lastAlgebraObject = object; } } return lastAlgebraObject; } Algebra.getNonDropBarObjectFromArray = function(array, desiredObjectIndex) { // this returns the nth occurrence of the non-drop bar objects in the array. if (!array || (array == null)) { reportNullObject("Algebra.getNonDropBarObjectFromArray(array, desiredObjectIndex)","array"); return null; } // It appears that an index of 0 is interpreted as null! So I've eliminated the test for that. // report("desiredObjectIndex = "+desiredObjectIndex+"\n"); // // if (!desiredObjectIndex || (desiredObjectIndex == null)) // { // reportNullObject("Algebra.getNonDropBarObjectFromArray(array, desiredObjectIndex)","desiredObjectIndex"); // return null; // } // Now go through the children of the groupObject and find the desiredObjectIndex-th non-drop-bar object. var numberOfObjects = array.length; var lastAlgebraObject = null; //report("Looking through "+numberOfObjects+" objects looking for non-drop-bar\n"); var indexNonDropBar = 0; for (var index = 0 ; index < numberOfObjects ; ++index) { var object = array[index]; //report("Looking at "+object.className+"\n"); if (!Algebra.isDropBar(object)) { if (indexNonDropBar == desiredObjectIndex) // This is it. //report("Found desired non-drop-bar, returning it.\n"); return(object); ++indexNonDropBar; } } return null; } Algebra.getSoleAlgebraObjectInGroup = function(groupObject) { //report("IN Algebra.getSoleAlgebraObjectInGroup(groupObject)("+groupObject.className+"\n"); if (!groupObject || (groupObject == null)) { reportNullObject("Algebra.getSoleAlgebraObjectInGroup(groupObject)","groupObject"); return null; } if (!groupObject.isGroupTypeObject || (groupObject.isGroupTypeObject == false)) { reportProgrammingError("Algebra.getSoleAlgebraObjectInGroup(groupObject)","groupObject NOT a group type object."); return null; } // Now go through the children of the groupObject and find the sole non-drop-bar object. // Actually, we find the FIRST one, since we assume that we have already determined that there is only one. var objects = groupObject.objects; var numberOfObjects = objects.length; //report("Looking through "+numberOfObjects+" objects looking for non-drop-bar\n"); //reportTabIn(); for (var index = 0 ; index < numberOfObjects ; ++index) { var object = objects[index]; //report("Looking at "+object.iD+"\n"); if (!Algebra.isDropBar(object)) { // This is it!. //report("Found a non-drop-bar, returning it ("+object.iD+".\n"); return(object); } } //reportTabOut(); //report("No algebra objects found, returning null\n"); return null; } Algebra.isSoleObjectOnSide = function(object) { report("IN Algebra.isSoleObjectOnSide(object)\n"); reportTabIn(); if (!object || (object == null)) { reportNullObject("Algebra.isSoleObjectOnSide(object)","object"); reportTabOut(); return false; } report("object = "+object.iD+"\n"); if (Algebra.isDropBar(object)) { reportProgrammingError("Algebra.isSoleObjectOnSide(object)","object is a Drop Bar. Should have passed in an actual object."); reportTabOut(); return false; } // Go UP from the obect and get the experssion parent, either GroupExpressionLeft or GroupExpressionRight. var expressionParent = Algebra.getGroupExpressionParent(object); if (!expressionParent || (expressionParent == null)) { reportNullObject("Algebra.isSoleObjectOnSide(object)","expressionParent"); reportTabOut(); return false; } report("expressionParent = "+expressionParent.iD+"\n"); // since the expression parent itself may have a hierarchical sequence of children which do not actually // have "contents" other than subsequent group objects (e.g. an expression parent that has a group sequence add/subtract // as its only child) get the first group DOWN the hierarchy which actually has contents. var groupWithContents = Algebra.getFirstGroupWithContentsDownTheHierarchy(expressionParent); report("groupWithContents = "+groupWithContents.iD+"\n"); var numberOfNonDropBars = Algebra.getNumberOfNonDropBarChildren(groupWithContents); report("numberOfNonDropBars = "+numberOfNonDropBars+"\n"); if (numberOfNonDropBars != 1) { report("numberOfNonDropBars NOT EQUAL to 1, so returning false.\n"); reportTabOut(); return false; } var soleChild = Algebra.getSoleAlgebraObjectInGroup(groupWithContents); report("soleChild = "+soleChild.iD+"\n"); if (soleChild == object) { report("Which is the object we are looking for, so returning true.\n"); reportTabOut(); return true; } report("Which is NOT the object we are looking for, so returning false.\n"); reportTabOut(); return false; } Algebra.getSoleAlgebraObjectInGroupExpression = function(groupExpressionObject) { // groupExpressionObject must be either algebra.equation.expressionSideLeft or algebra.equation.expressionSideRight // which is Algebra.GroupExpressionLeft or ALgebra.GroupExpressionRight. // //report("IN Algebra.getSoleAlgebraObjectInGroupExpression("+groupExpressionObject.className+")\n"); //reportTabIn(); if (!groupExpressionObject || (groupExpressionObject == null)) { //reportTabOut(); reportNullObject("Algebra.getSoleAlgebraObjectInGroupExpression(expressionObject)","groupExpressionObject"); return null; } if (!((groupExpressionObject instanceof Algebra.GroupExpressionLeft) || (groupExpressionObject instanceof Algebra.GroupExpressionRight))) { //reportTabOut(); reportProgrammingError("Algebra.getSoleAlgebraObjectInGroupExpression(groupExpressionObject)","groupExpressionObject NOT an Expression type object."); return null; } // Now go through the children of the expressionObject and find the sole non-drop-bar object. // Actually, we find the FIRST one, since we assume that we have already determined that there is only one. var objects = groupExpressionObject.objects; var numberOfObjects = objects.length; report("Looking through "+numberOfObjects+" objects looking for non-drop-bar\n"); for (var index = 0 ; index < numberOfObjects ; ++index) { var object = objects[index]; report("Looking at "+object.iD+"\n"); if (!Algebra.isDropBar(object)) { // This is it!. report("Found a non-drop-bar, returning it.\n"); //reportTabOut(); return(object); } } //reportTabOut(); //report("No algebra objects found, returning null\n"); return null; } Algebra.getNumberOfAlgebraChildren = function(parentObject) { //report("IN Algebra.getNumberOfAlgebraChildren("+parentObject.iD+")\n"); // Given the parentObject this returns the number of children of the // object that are actually algebra objects. In particular, it excludes // drop bars. if (!parentObject || (parentObject == null)) { reportNullObject("Algebra.getNumberOfAlgebraChildren(parentObject)","parentObject"); return 0; } if (!parentObject.objects) { reportNullObject("Algebra.getNumberOfAlgebraChildren(parentObject)","parentObject.objects"); // This parentObject does not have a set of objects. return 0; } var numberOfAlgebraObjects = 0; var objects = parentObject.objects; var numberOfObjects = parentObject.objects.length; //report("Lookin through "+numberOfObjects+" children and counting non-drop-bar objects.\n"); for (index = 0 ; index < numberOfObjects ; ++index) { var childObject = objects[index]; //report("Looking at "+childObject.iD+"\n"); if (Algebra.isAlgebraObject(childObject)) { //report("Count one more non-drop object.\n"); ++numberOfAlgebraObjects; } } return numberOfAlgebraObjects; } Algebra.getNonDropBarChild = function(parentObject, desiredChildIndex) { //report("IN Algebra.getNonDropBarChild("+parentObject.iD+", "+desiredChildIndex+")\n"); //reportTabIn(); // Given the parentObject this returns the Nth non-dropbar child of the parentObject. if (!parentObject || (parentObject == null)) { reportNullObject("Algebra.getNonDropBarChild(parentObject, desiredChildIndex)","parentObject"); //reportTabOut(); return null; } if (!parentObject.objects) { reportNullObject("Algebra.getNonDropBarChild(parentObject, desiredChildIndex)","parentObject.objects"); // This parentObject does not have a set of objects. //reportTabOut(); return null; } if ((!desiredChildIndex || (desiredChildIndex == null)) && (desiredChildIndex != 0)) { reportNullObject("Algebra.getNonDropBarChild(parentObject, desiredChildIndex)","desiredChildIndex"); //reportTabOut(); return null; } var nonDropBarIndex = 0; var objects = parentObject.objects; var numberOfObjects = parentObject.objects.length; //report("Looking through "+numberOfObjects+" children for the "+desiredChildIndex+"th non-drop-bar object.\n"); //reportTabIn(); for (index = 0 ; index < numberOfObjects ; ++index) { var childObject = objects[index]; //report("Looking at "+childObject.className+"\n"); if (!Algebra.isDropBar(childObject)) { if (nonDropBarIndex == desiredChildIndex) { return childObject; } ++nonDropBarIndex; } } //reportTabOut(); //reportTabOut(); //report("EXITING Algebra.getNonDropBarChild(parentObject, desiredChildIndex)\n"); return null; } Algebra.getNumberOfNonDropBarChildren = function(parentObject) { // NOTE This is NOT identical to getNumberOfAlgebraChildren(). // The getNumberOfAlgebraChildren only counts things like Integer, Variable, Fraction... // It does NOT count the Operators. // This function,on the other hand, counts everything BUT the drop bars. //report("IN Algebra.getNumberOfNonDropBarChildren("+parentObject.iD+")\n"); //reportTabIn(); // Given the parentObject this returns the number of children of the // object that are not DropBar objects. if (!parentObject || (parentObject == null)) { reportNullObject("Algebra.getNumberOfNonDropBarChildren(parentObject)","parentObject"); //reportTabOut(); return 0; } if (!parentObject.objects) { reportNullObject("Algebra.getNumberOfNonDropBarChildren(parentObject)","parentObject.objects"); // This parentObject does not have a set of objects. //reportTabOut(); return 0; } var numberOfNonDropBarObjects = 0; var objects = parentObject.objects; var numberOfObjects = parentObject.objects.length; //report("Looking through "+numberOfObjects+" children and counting non-drop-bar objects.\n"); //reportTabIn(); for (index = 0 ; index < numberOfObjects ; ++index) { var childObject = objects[index]; //report("Looking at "+childObject.iD+"\n"); if (!Algebra.isDropBar(childObject)) { //report("Count one more non-drop object.\n"); ++numberOfNonDropBarObjects; } } //reportTabOut(); //report("returning numberOfNonDropBarObjects = "+numberOfNonDropBarObjects+"\n"); //reportTabOut(); //report("EXITING Algebra.getNumberOfNonDropBarChildren("+parentObject.iD+")\n"); return numberOfNonDropBarObjects; } Algebra.getNumberOfAlgebraObjectsInArray = function(array) { //report("IN getNumberOfAlgebraObjectsInArray(array)\n"); // Given the array this returns the number of objects that are actually algebra objects. In particular, it excludes // drop bars. if (!array || (array == null)) // || !array.length) || (array.length == 0)) Actually ARE cases where zero objects is OK. { reportNullObject("Algebra.getNumberOfAlgebraObjectsInArray(array)","array"); return 0; } if (!array.length || (array.length == 0)) { return 0; } var numberOfAlgebraObjects = 0; var numberOfObjects = array.length; //report("Lookin through "+numberOfObjects+" objects and counting non-drop-bar objects.\n"); for (index = 0 ; index < numberOfObjects ; ++index) { var object = array[index]; //report("Looking at "+object.iD+"\n"); if (Algebra.isAlgebraObject(object)) { //report("Count one more non-drop object.\n"); ++numberOfAlgebraObjects; } } return numberOfAlgebraObjects; } Algebra.getNumberOfNonDropBarObjectsInArray = function(array) { if (!array || (array == null)) { reportNullObject("Algebra.getNumberOfNonDropBarObjectsInArray(array)","array"); return 0; } if (!(array instanceof Array)) { reportProgrammingError("Algebra.getNumberOfNonDropBarObjectsInArray(array)","array is not a Array object"); return 0; } var numberOfElements = array.length; var numberOfNonDropBarObjects = 0; for (var index = 0 ; index < numberOfElements ; ++index) { var thisObject = array[index]; if (!Algebra.isDropBar(thisObject)) { ++numberOfNonDropBarObjects; } } return numberOfNonDropBarObjects; } Algebra.getNumberOfElementaryObjectsTotal = function(object) { // This traverses the hierarchy DOWNWARDS, starting at the specified object, and returns the // total number of Algebra Objects (i.e. actual Numbers and Variables and Constants) if (!object || (object == null)) { reportNullObject("Algebra.getNumberOfElementaryObjectsTotal(object)","object"); return 0; } if (Algebra.isElementaryObject(object)) { return 1; } if (object.isGroupTypeObject && (object.isGroupTypeObject == true) && object.objects && object.objects.length) { var numberOfElementaryObjects = 0; var objects = object.objects; var numberOfObjects = objects.length; for (var index = 0 ; index < numberOfObjects ; ++ index) { var thisObject = objects[index]; numberOfElementaryObjects += Algebra.getNumberOfElementaryObjectsTotal(thisObject); } return numberOfElementaryObjects; } return 0; } Algebra.getNumberOfElementaryObjects = function(object) { var numberOfElementaryObjects = 0; if (!object || (object == null)) { reportNullObject("Algebra.getNumberOfElementaryObjects(object)","object"); return 0; } if (Algebra.isElementaryObject(object)) { return 1; } if (object.isGroupTypeObject && (object.isGroupTypeObject == true) && object.objects && object.objects.length) { var objects = object.objects; var numberOfObjects = objects.length; for (var index = 0 ; index < numberOfObjects ; ++ index) { var thisObject = objects[index]; if (Algebra.isElementaryObject(thisObject)) { ++numberOfElementaryObjects; } } } return numberOfElementaryObjects; } Algebra.getGroupParent = function(object) { //report("IN Algebra.getGroupParent("+object.iD+")\n"); //reportTabIn(); // This function attempts to go up the hierarchy and find the first "group" // type of object on its way up. If found it returns that object. if (!object) { // NOTE, maybe do not report error. Since, when it gets to the very top there // in fact IS no parent object. //reportNullObject("Algebra.getGroupParent","object"); //report("Return from Algebra.getGroupParent with NULL because parameter to function is NULL\n"); //reportTabOut(); return null; } var parentObjectID = object.parentObjectId; if (!parentObjectID || (parentObjectID == null) || (parentObjectID.length == 0)) { // When it gets to the very top there // in fact IS no parent object. //reportTabOut(); return null; } //report("parentObjectID = " + parentObjectID + "\n"); //var parentObject = Algebra.getObjectById(g_myAlgebraObject.equation, parentObjectID); var parentObject = object.parentObject; if (!parentObject || (parentObject == null)) { reportNullObject("Algebra.getGroupParent", "parentObject"); report("parentObjectID = " + parentObjectID + "\n"); //report("Return from Algebra.getGroupParent with NULL because parentObject not found by Algebra.getObjectById.\n"); return null; } if (Algebra.isGroupTypeObject(parentObject)) { //report("returning with group parent " + parentObject.className + " = " + parentObject.iD + "\n"); //reportTabOut(); return parentObject; } // otherwise, recurse up the hierarchy. //reportTabOut(); return Algebra.getGroupParent(parentObject); } Algebra.isGroupTypeObject = function(object) { if (!object) { reportNullObject("Algebra.isGroupTypeObject","object"); return false; } //report("IN Algebra.isGroupTypeObject(object) with " + object.className + " = " + object.value + "\n"); //Note that non-group type objects should not even HAVE the isGroupTtypeObject property. if (object.isGroupTypeObject && (object.isGroupTypeObject == true)) { //report("IS a group type object. Returning TRUE.\n"); return true } //report("NOT a group type object. Returning FALSE.\n"); return false; } var k_neighborDirectionLeft = 1; var k_neighborDirectionRight = 2; Algebra.getNeighbor = function(objectIdLookingFor, neighborDirection) { // Given the ID of an Algebra object this returns the object // to the left or right (specified by neighborDirection) of that object in the array of objects // in which that object resides. // If there is no array of objects within which that object resides or // no object to its left or right then this returns null. //report("IN Algebra.getNeighbor("+objectIdLookingFor+")\n"); //report("g_myAlgebraObject.equation = \n"+g_myAlgebraObject.equation.toHTML()+"\n"); //reportTabIn(); if (!objectIdLookingFor || !objectIdLookingFor.length || objectIdLookingFor.length == 0) { reportNullObject("Algebra.getNeighbor","objectIdLookingFor"); //reportTabOut(); return null; } //report("About to Algebra.getObjectById("+objectIdLookingFor+")\n"); var object = Algebra.getObjectById(g_myAlgebraObject.equation, objectIdLookingFor); if (!object || object == null) { reportNullObject("Algebra.getNeighbor","object"); //reportTabOut(); return null; } if (!neighborDirection || neighborDirection == null) { reportNullObject("Algebra.getNeighbor","neighborDirection"); //reportTabOut(); return null; } if (! ((neighborDirection == k_neighborDirectionLeft) || (neighborDirection == k_neighborDirectionRight))) { reportProgrammingError("Algebra.getNeighbor","Invalid neighborDirection parameter"); //reportTabOut(); return null; } //report("object = "+object.className + " = " + object.iD + "\n"); var groupParent = Algebra.getGroupParent(object); //report("releated group parent object = "+groupParent.className + " = " + groupParent.iD + "\n"); if (!groupParent || (groupParent == null) || !groupParent.objects || (groupParent.objects == null)) { //report("No objects array for the groupParent object\n"); //reportTabOut(); return null; } var objects = groupParent.objects; var objectToLeft = null; var objectToRight = null; var numberOfObjects = objects.length; //report("Looking through array of objects with "+numberOfObjects+" objects.\n"); for (var index = 0 ; index < numberOfObjects ; ++index) { var thisObject = objects[index]; //report("Looking at "+thisObject.className+ " = " +thisObject.iD+"\n"); if (thisObject.iD && thisObject.iD == objectIdLookingFor) { //report("Which is the object that we are looking for.\n"); if (neighborDirection == k_neighborDirectionLeft) { if (objectToLeft != null) { //report("Returning objectToLeft = "+objectToLeft.className + " : " + objectToLeft.iD + "\n"); } //reportTabOut(); return objectToLeft; } else { ++index; //report("Incremented index to "+index+" (out of "+numberOfObjects+" objects\n"); if (index < numberOfObjects) { objectToRight = objects[index]; //report("Returning objectToRight = "+objectToRight.className + " : " + objectToRight.iD + "\n"); } //reportTabOut(); return objectToRight; } //report("Returning null\n"); return null; } objectToLeft = thisObject; } //reportTabOut(); //report("Returning null\n"); return null; } ////////////////////////////////////////////////////// // // OBJECT SIDE STUFF // /////////////////////////////////////////////////////// var k_dragItemSourceSideNone = 0; var k_dragItemSourceSideExpressionLeft = 1; var k_dragItemSourceSideExpressionRight = 2; var g_dragItemSourceSide; Algebra.determineDragItemSourceSide = function(objectId) { // WARNING NOTE: This would go much faster if I just traverses UP the hierarchy using the parentObject references. g_dragItemSourceSide = k_dragItemSourceSideNone; object = Algebra.getObjectById(g_myAlgebraObject.equation.expressionSideLeft, objectId); if (object != null) { g_dragItemSourceSide = k_dragItemSourceSideExpressionLeft; } else { object = Algebra.getObjectById(g_myAlgebraObject.equation.expressionSideRight, objectId); if (object != null) { g_dragItemSourceSide = k_dragItemSourceSideExpressionRight; } } return g_dragItemSourceSide; } Algebra.dragItemSourceIsLeftExpression = function() { return (g_dragItemSourceSide == k_dragItemSourceSideExpressionLeft); } Algebra.dragItemSourceIsRightExpression = function() { return (g_dragItemSourceSide == k_dragItemSourceSideExpressionRight); } var k_isInExpressionSideUnknown = 0; var k_isInExpressionSideLeft = 1; var k_isInExpressionSideRight = 2; Algebra.getMouseLocationSide = function(evt) { if (!evt) { reportNullObject("Algebra.getMouseLocationSide(evt)","evt"); return k_isInExpressionSideUnknown; } var mousePosition = getMousePositionRelativeToContainer(evt, "ManipulationArea"); if (!mousePosition || (mousePosition == null)) { reportNullObject("Algebra.getMouseLocationSide(evt)","mousePosition"); return k_isInExpressionSideUnknown; } if (mousePosition.left > g_manipulationAreaVerticalCenter) { return k_isInExpressionSideRight; } return k_isInExpressionSideLeft; } var k_isInExpressionUpperLower_Unknown = 0; var k_isInExpressionUpperLower_Upper = 1; var k_isInExpressionUpperLower_Lower = 2; Algebra.getMouseLocationUpperLower = function(evt) { // returns either: // k_isInExpressionUpperLower_Unknown // k_isInExpressionUpperLower_Upper // k_isInExpressionUpperLower_Lower // // indicating the position of the mouse relative to the top // of the DropBarHorizontal that is located beneath the expression. // This is used for determining whe to flip the dragged object from // its plus/minus version to its denominator version (but only under // certain special circumstances). if (!evt) { reportNullObject("Algebra.getMouseLocationUpperLower(evt)","evt"); return k_isInExpressionUpperLower_Unknown; } var mousePosition = getMousePositionRelativeToContainer(evt, "ManipulationArea"); if (!mousePosition || (mousePosition == null)) { reportNullObject("Algebra.getMouseLocationUpperLower(evt)","mousePosition"); return k_isInExpressionUpperLower_Unknown; } var horizontalDropBarPositionTop = 0; var mouseLocationSide = Algebra.getMouseLocationSide(evt); if (mouseLocationSide == k_isInExpressionSideLeft) { horizontalDropBarPositionTop = g_HorizontalDropBarPositionTop_ExpressionLeft; } else if (mouseLocationSide == k_isInExpressionSideRight) { horizontalDropBarPositionTop = g_HorizontalDropBarPositionTop_ExpressionRight; } else { reportProgammingError("Algebra.getMouseLocationUpperLower(evt)","Failed to Algebra.getMouseLocationSide()"); return k_isInExpressionUpperLower_Unknown; } if (mousePosition.top > horizontalDropBarPositionTop) { //adviseUser("BELOW DROP BAR HORIZONTAL", k_adviseUserImportance_Normal); //report("BELOW DROP BAR HORIZONTAL\n"); return k_isInExpressionUpperLower_Lower; } //adviseUser("ABOVE DROP BAR HORIZONTAL", k_adviseUserImportance_Normal); //report("ABOVE DROP BAR HORIZONTAL\n"); return k_isInExpressionUpperLower_Upper; } Algebra.isObjectOnLeftSide = function(objectId) { //report("IN Algebra.isObjectOnLeftSide("+objectId+")\n"); if (!objectId || (objectId.length == 0)) { reportProgrammingError("Algebra.isObjectOnLeftSide(objectId)","objectId"); return false; } var position = getHTMLObjectPositionRelativeToContainer("ManipulationArea", objectId); //report("Object at "+position.left+" Center at "+g_manipulationAreaVerticalCenter+"\n"); if (position.left < g_manipulationAreaVerticalCenter) { //report("IS LEFT\n"); return true; } //report("NOT LEFT\n"); return false; } Algebra.isObjectOnRightSide = function(objectId) { //report("IN Algebra.isObjectOnRightSide("+objectId+")\n"); if (!objectId || (objectId.length == 0)) { reportProgrammingError("Algebra.isObjectOnRightSide(object)","objectId"); return false; } var position = getHTMLObjectPositionRelativeToContainer("ManipulationArea", objectId); //report("Object at "+position.left+" Center at "+g_manipulationAreaVerticalCenter+"\n"); if (position.left > g_manipulationAreaVerticalCenter) { //report("IS RIGHT\n"); return true; } //report("NOT RIGHT\n"); return false; } Algebra.getObjectLocationSide = function(objectId) { //report("IN Algebra.getObjectLocationSide("+objectId+")\n"); if (!objectId || !objectId.length || (objectId.length == 0)) { reportNullObject("Algebra.getObjectLocationSide(objectId)","objectId"); return null; } if (Algebra.isObjectOnLeftSide(objectId)) { //report("LEFT SIDE\n"); return k_isInExpressionSideLeft; } else if (Algebra.isObjectOnRightSide(objectId)) { //report("RIGHT SIDE\n"); return k_isInExpressionSideRight; } //report("SIDE UNKNOWN!!\n"); return k_isInExpressionSideUnknown; } var g_dragObjectSideCurrent = null; var g_dragObjectSideSource = null; var g_dragObjectUpperLowerCurrent = null; Algebra.handleSideAndUpperLowerSwitch = function(evt) { //report("IN Algebra.handleSideAndUpperLowerSwitch("+objectId+")\n"); if (!evt || (evt == null)) { reportNullObject("Algebra.handleSideAndUpperLowerSwitch(evt)","evt"); return; } //////////////// // // SIDE-TO-SIDE // //////////////// var mouseLocationSide = Algebra.getMouseLocationSide(evt); if (g_dragObjectSideCurrent == null) { // First time doing this function for this drag object. g_dragObjectSideCurrent = mouseLocationSide; return; } if (mouseLocationSide != g_dragObjectSideCurrent) { // We changed sides. var dragItem = document.getElementById("DragItem"); if (mouseLocationSide == g_dragObjectSideSource) { // We changed BACK to the drag object source side. //adviseUser("SOURCE SIDE", k_adviseUserImportance_Normal); soundPlay("dragObjectToSideSource"); dragItem.innerHTML = g_dragItemHTMLCodeForSideSource; //report("g_dragItemHTMLCodeForSideSource = \n"+g_dragItemHTMLCodeForSideSource+"\n"); } else { // We changed TO the drag object target side. //adviseUser("TARGET SIDE", k_adviseUserImportance_Normal); soundPlay("dragObjectToSideTarget"); dragItem.innerHTML = g_dragItemHTMLCodeForSideTarget; //report("g_dragItemHTMLCodeForSideTarget = \n"+g_dragItemHTMLCodeForSideTarget+"\n"); } g_dragObjectSideCurrent = mouseLocationSide; } /////////////// // // UPPER-LOWER // /////////////// var mouseLocationUpperLower = Algebra.getMouseLocationUpperLower(evt); if (g_dragObjectUpperLowerCurrent == null) { // First time doing this function for this drag object. g_dragObjectUpperLowerCurrent = mouseLocationUpperLower; return; } // The upper/lower distinction is only relavant when the mouse is in the target side // AND we actually HAVE a special version of the drag item for the LOWER side. //report("Check upper/lower?\n"); //report("g_dragObjectSideCurrent = "+g_dragObjectSideCurrent+"\n"); //report("g_dragObjectSideSource "+g_dragObjectSideSource+"\n"); //report("g_dragItemHTMLCodeForSideTargetLower = "+g_dragItemHTMLCodeForSideTargetLower+"\n"); if ((g_dragObjectSideCurrent != g_dragObjectSideSource) && (g_dragItemHTMLCodeForSideTargetLower != "")) { //report("YES, need to check upper/lower?\n"); //report("g_dragObjectUpperLowerCurrent = "+g_dragObjectUpperLowerCurrent+"\n"); //report("mouseLocationUpperLower = "+mouseLocationUpperLower+"\n"); if (mouseLocationUpperLower != g_dragObjectUpperLowerCurrent) { // We changed upper/lower. //report("\n>>>We changed upper/lower>>>\n\n"); g_dragObjectUpperLowerCurrent = mouseLocationUpperLower; var dragItem = document.getElementById("DragItem"); if (mouseLocationUpperLower == k_isInExpressionUpperLower_Upper) { // We changed to UPPER. //adviseUser("Changed to Target Upper", k_adviseUserImportance_Normal); //report("Changed to Target Upper\n"); soundPlay("dragObjectLowerToUpper"); dragItem.innerHTML = g_dragItemHTMLCodeForSideTarget; //report("g_dragItemHTMLCodeForSideSource = \n"+g_dragItemHTMLCodeForSideSource+"\n"); } else if (mouseLocationUpperLower == k_isInExpressionUpperLower_Lower) { // We changed TO LOWER. //adviseUser("Changed to Target Lower", k_adviseUserImportance_Normal); //report("Changed to Target Lower\n"); soundPlay("dragObjectUpperToLower"); dragItem.innerHTML = g_dragItemHTMLCodeForSideTargetLower; //report("g_dragItemHTMLCodeForSideTarget = \n"+g_dragItemHTMLCodeForSideTarget+"\n"); } } else { //report("Did NOT change upper/lower\n"); } } } Algebra.initializeSideSwitch = function(objectId) { //report("IN Algebra.initializeSideSwitch("+objectId+")\n"); g_dragObjectSideSource = Algebra.getObjectLocationSide(objectId); g_dragObjectSideCurrent = g_dragObjectSideSource; g_dragObjectUpperLowerCurrent = null; } ////////////////////////////////////////////////////////////////// // // POSITION METHODS // ////////////////////////////////////////////////////////////////// function getAlgebraObjectPositionRelativeToDocument(algebraObjectId) { // Given the specified Algebra object Id, this calculates the absolute position of its related HTML element, relative to its top-most parent, the document. if (!algebraObjectId || algebraObjectId.length == 0) { reportNullObject("getAlgebraObjectPositionRelativeToDocument(algebraObjectId)","algebraObjectId"); return null; } // We need to get the related HTML object, which SHOULD have the same ID as the Algebra object. var HTMLObjectId = algebraObjectId; var position = getHTMLObjectPositionRelativeToDocument(hTMLObjectId); if (position == null) { reportNullObject("getAlgebraObjectPositionRelativeToDocument(algebraObjectId)","position for "+hTMLObjectId); return null; } return position; } function getHTMLObjectPositionRelativeToDocument(hTMLObjectId) { // Given the specified HTML object, this calculates the absolute position of it, relative to its top-most parent, the document. if (!hTMLObjectId || hTMLObjectId.length == 0) { reportNullObject("getHTMLObjectPositionRelativeToDocument(hTMLObjectId)","hTMLObjectId"); return null; } var left = 0; var top = 0; var hTMLObject = document.getElementById(hTMLObjectId); if (!hTMLObject) { reportNullObject("getHTMLObjectPositionRelativeToDocument(hTMLObjectId)","hTMLObject for "+hTMLObjectId); return null; } var width = hTMLObject.offsetWidth; var height = hTMLObject.offsetHeight; // Travel up through the sequence of parents, consecutively adding each parent's offset to // the x,y position. while (hTMLObject.offsetParent) { // NOTE Does this also need to take into account the scrolling status of the items? left += hTMLObject.offsetLeft; top += hTMLObject.offsetTop; hTMLObject = hTMLObject.offsetParent; } // Add the final parent's offsets. left += hTMLObject.offsetLeft; top += hTMLObject.offsetTop; //report("IN getHTMLObjectPositionRelativeToDocument("+hTMLObjectId+") Returning " + left + "," + top + " " + width + "X " + height + "\n"); return {left:left, top:top, width:width, height:height}; } function getMousePositionRelativeToDocument(ev) { // Get and return the position of the mouse position // calculated as relative to the upper left corner of the document, taking into account the scrolled position of the document. if (ev == null) { reportNullObject("getMousePositionRelativeToDocument","ev"); return null; } var left; var top; if(ev.pageX || ev.pageY) { // The ev.pageX and ev.pageY values are indeed available, indicating that we are NOT in Internet Explorer, // so return those values. They already take into account the scrolling of the document. left = ev.pageX; top = ev.pageY; } else { // It appears that we're in Internet explorer, which uses event,clientX and event.ClientY // as the position values. left= ev.clientX + document.body.scrollLeft - document.body.clientLeft; top = ev.clientY + document.body.scrollTop - document.body.clientTop; }; //report("IN getMousePositionRelativeToDocument(ev) Position = "+left+","+top+"\n"); return {left:left, top:top}; } function getMousePositionRelativeToContainer(ev, hTMLContainerId) { if (!hTMLContainerId || (hTMLContainerId.length == 0)) { reportNullObject("getMousePositionRelativeToContainer(ev, hTMLContainerId)","hTMLContainerId"); return null; } var mousePosition = getMousePositionRelativeToDocument(ev); var hTMLContainerPosition = getHTMLObjectPositionRelativeToDocument(hTMLContainerId); if (hTMLContainerPosition == null) { reportNullObject("getMousePositionRelativeToContainer(ev, hTMLContainerId)","hTMLContainerPosition for "+hTMLContainerId); return null; } var left = mousePosition.left - hTMLContainerPosition.left; var top = mousePosition.top - hTMLContainerPosition.top; //report("IN getMousePositionRelativeToContainer(ev, "+hTMLContainerId+") position = "+left+","+top+"\n"); return {left:left, top:top}; } function getHTMLObjectPositionRelativeToContainer(hTMLContainerObjectId, hTMLObjectId) { // Calculate and return the position of the object // relative to the container. if (!hTMLContainerObjectId || !hTMLContainerObjectId.length || (hTMLContainerObjectId.length == 0) || !hTMLObjectId || (hTMLObjectId.length == 0)) { reportNullObject("getHTMLObjectPositionRelativeToContainer(hTMLContainerObjectId, hTMLObjectId)","hTMLContainerObjectId = "+hTMLContainerObjectId+" OR hTMLObjectId = "+hTMLObjectId); return null; } var hTMLContainerPosition = getHTMLObjectPositionRelativeToDocument(hTMLContainerObjectId); var hTMLObjectPosition = getHTMLObjectPositionRelativeToDocument(hTMLObjectId); var left = hTMLObjectPosition.left - hTMLContainerPosition.left; var top = hTMLObjectPosition.top - hTMLContainerPosition.top; //report("IN getHTMLObjectPositionRelativeToContainer("+hTMLContainerObjectId+", "+hTMLObjectId+") Position = "+left+","+top+"\n"); return {left:left, top:top}; } /////////////////////////////////////////////////////////////////// // // EVENT PROPERTIES // // altKey Retrieves a value that indicates the state of the ALT key. // altLeft Retrieves a value that indicates the state of the left ALT key. // cancelBubble Sets or retrieves whether the current event should bubble up the hierarchy of event handlers. // clientX Sets or retrieves the x-coordinate of the mouse pointer's position relative to the client area of the window, excluding window decorations and scroll bars. // clientY Sets or retrieves the y-coordinate of the mouse pointer's position relative to the client area of the window, excluding window decorations and scroll bars. // ctrlKey Sets or retrieves the state of the CTRL key. // ctrlLeft Sets or retrieves the state of the left CTRL key. // offsetX Sets or retrieves the x-coordinate of the mouse pointer's position relative to the object firing the event. // offsetY Sets or retrieves the y-coordinate of the mouse pointer's position relative to the object firing the event. // screenX Sets or retrieves the x-coordinate of the mouse pointer's position relative to the user's screen. // screenY Sets or retrieves the y-coordinate of the mouse pointer's position relative to the user's screen. // shiftKey Retrieves the state of the SHIFT key. // shiftLeft Retrieves the state of the left SHIFT key. // srcElement Sets or retrieves the object that fired the event. // type Sets or retrieves the event name from the event object. // x Sets or retrieves the x-coordinate (in pixels) of the mouse pointer's offset from the closest relatively positioned parent element of the element that fired the event. // y Sets or retrieves the y-coordinate (in pixels) of the mouse pointer's offset from the closest relatively positioned parent element of the element that fired the event. var g_isMouseButtonDown = false; // If the number of onmousemove calls between a mouse down and a mouse up exceeds // the k_minimumNumberOfMovesBeforeDrag then subsequent mouse moves are determined // to be drags. var k_minimumNumberOfMovesBeforeDrag = 4; // If the number of onmousemove calls between a mouse down and a mouse up is less than // the k_maximumNumberOfMovesForClick then the mouseup is determined to be a click. var k_maximumNumberOfMovesForClick = k_minimumNumberOfMovesBeforeDrag; var g_numberOfMouseMovesBetweenMouseDownAndUp = 0; ///////////////////////////////////////////////////// // // SELECTED OBJECTS ARRAY // ///////////////////////////////////////////////////// function SelectedObjects() { this.objects = new Array(); this.length = 0; } SelectedObjects.prototype.add = function(objectId, object) { // Adds the object if it is not already in the array. //report("IN SelectedObjects.prototype.add("+objectId+")\n"); //reportTabIn(); //report("Before adding:\n"); //this.report(); if (!objectId || !objectId.length || (objectId.length == 0)) { reportNullObject("SelectedObjects.prototype.add(objectId, object)","objectId [ perhaps forgot a parameter ? ]"); //reportTabOut(); return false; } if (!object || (object == null)) { reportNullObject("SelectedObjects.prototype.add(objectId, object)","object [ perhaps forgot a parameter ? ]"); //reportTabOut(); returnfalse; } // Do not include it if it's already included. if (!this.includes(objectId)) { // Nope, not yet included, so include it. this.objects[objectId] = object; ++this.length; //report("After adding:\n"); //this.report(); //reportTabOut(); return true; } else { //report("NOT ADDED because already exists!!!\n"); //this.report(); //reportTabOut(); return false; } //report("After adding:\n"); //this.report(); //reportTabOut(); } SelectedObjects.prototype.remove = function(objectId) { //report("IN SelectedObjects.prototype.remove(" + objectId + ")\n"); //reportTabIn(); //report("Before removing:\n"); //this.report(); if (this.includes(objectId)) { // Let's try this. // Create a new array and move everything over that is NOT the one // that we're trying to remove. var newArray = new Array(); for(var associativeIndex in this.objects) { if (associativeIndex != objectId) { newArray[associativeIndex] = this.objects[associativeIndex]; } } this.objects = newArray; --this.length; } //report("After removing:\n"); //this.report(); //reportTabOut(); } SelectedObjects.prototype.includes = function(objectId) { //report("IN SelectedObjects.prototype.includes\n"); // returns true if the specified object is already in the array. var isInArray = (this.objects[objectId] != null); // TEST START if (isInArray) { //report(objectId + " is in array\n"); } else { //report(objectId + " NOT in array\n"); } // TEST END return isInArray; } SelectedObjects.prototype.empty = function() { //report("IN SelectedObjects.prototype.empty()\n"); //reportTabIn(); //report("Before emptying:\n"); //this.report(); // I'm forced to actually declare an entirely new array! this.objects = new Array(); //report("After emptying:\n"); //this.report(); this.length = 0; } SelectedObjects.prototype.getObjectArray = function() { // This returns an array of JUST the objects. // //report("IN SelectedObjects.prototype.getObjectArray()\n"); //reportTabIn(); var newArray = new Array(); var index = 0; for(var associativeIndex in this.objects) { newArray[index] = this.objects[associativeIndex]; ++index; } //report("Returnig newArray with "+newArray.length+" objects.\n"); //reportTabOut(); return newArray; } SelectedObjects.prototype.getNumberOfAlgebraObjects = function() { //report("IN SelectedObjects.prototype.getNumberOfAlgebraObjects()\n"); //reportTabIn(); //this.report(); var theObjects = this.getObjectArray(); //report("theObjects.length = "+theObjects.length+"\n"); var numberOfAlgebraObjects = 0; //reportTabIn(); for (var index = 0 ; index < theObjects.length ; ++index) { var thisObject = theObjects[index]; //report(thisObject.className+"\n"); if (Algebra.isAlgebraObject(thisObject)) { ++numberOfAlgebraObjects; } } //reportTabOut(); //report("Returning numberOfAlgebraObjects = "+numberOfAlgebraObjects+"\n"); //reportTabOut(); return numberOfAlgebraObjects; } SelectedObjects.prototype.toString = function() { //report("IN SelectedObjects.prototype.toString()\n"); var string = "Selected Objects Array START:\n"; var numberOfObjects = 0; for(var index in this.objects) { //report("index = "+index+"\n"); var object = this.objects[index]; //report("object = "+object.className+" : "+object.iD+"n"); //string += " " + index + " Position left = " + object.positionRelativeToContainer.left + " top = " + object.positionRelativeToContainer.top + "\n"; string += " " + index + " : "+object.className+" : "+object.iD+"\n"; ++numberOfObjects; // NOTE If we ever get an ERROR regarding the positionRelativeToContainer not bing defined or being NULL it's beacuse we // added an object to the SelectedObjects but forgot to set its positionRelativeToContainer property before adding. // MAYBE should just make that a required paremeter to the SelectedObjects.add() function!!! } if (numberOfObjects == 0) { string += " EMPTY\n"; } string += "Selected Objects Array END\n"; return string; } SelectedObjects.prototype.report = function() { //report("IN SelectedObjects.prototype.report() about to this.toString()\n"); //report(this.toString()); report("\n"); report(">>> SELECTED OBJECTS:\n"); reportTabIn(); for(var objectId in this.objects) { var object = this.objects[objectId]; var text = ""; text = object.className+" : "+object.iD; if (object.positionRelativeToContainer) { text += " at "+ object.positionRelativeToContainer.left + "," + object.positionRelativeToContainer.top; } text += "\n"; report(text); } reportTabOut(); report("<<< SELECTED OBJECTS\n\n"); } SelectedObjects.prototype.getObject = function(index) { // return the indexed object from the actual array. if (index < this.length) { var ndx = 0; for (var key in this.objects) { if (ndx == index) { return this.objects[key]; } ++ndx; } } reportProgrammingError("selectedObjects.getObject(index)","index "+index+" is out of bounds = "+this.objects.length); return null; } SelectedObjects.prototype.numberOfAlgebraObjects = function() { // Returns the number of Algebra Objects (excludes operators) var numberOfAlgebraObjects = 0; for (var objectId in this.objects) { var thisObject = this.objects[objectId]; if (Algebra.isAlgebraObject(thisObject)) { ++numberOfAlgebraObjects; } } return numberOfAlgebraObjects; } SelectedObjects.prototype.includesOperatorMultiplyExplicit = function() { // Returns true if the array contains one or more OperatorMultiply objects. for (var objectId in this.objects) { var thisObject = this.objects[objectId]; if (thisObject instanceof Algebra.OperatorMultiply) { return true; } } return false; } var g_mySelectedObjects = new SelectedObjects(); //////////////////////////////////////////////////////////////// // // MOUSE EVENT HANDLING // //////////////////////////////////////////////////////////////// var g_isDragging = false; Algebra.clearMouseParameters = function() { //report("IN Algebra.clearMouseParameters()\n"); g_mouseDownObjectId = null; g_isMouseButtonDown = false; g_numberOfMouseMovesBetweenMouseDownAndUp = 0; // NO !!! g_mySelectedObjects.empty(); //report("IN Algebra.clearMouseParameters() setting g_isDragging = false\n"); g_isDragging = false; } /////////////////////////////////////////////////////////////////// // // ON MOUSE ENTER // /////////////////////////////////////////////////////////////////// Algebra.onMouseEnter = function(evt) { // NOTE that a mouse move can occur before the mouse enter. // NOTE WARNING This gets triggered on EVERY LEAVE, for EVERY HTML object // within the Equation Manipulation Box, not just for the box itself!!!! if (g_isInStepBack) return false; var evt = evt || window.event; if (!evt) { return true; // continue to other elements } var srcElement = evt.target || evt.srcElement; //report("Algebra.onMouseEnter("+srcElement.id+"\n"); //adviseUser("Algebra.onMouseEnter("+srcElement.id+")", k_adviseUserImportance_Normal); if (!Algebra.isOnAlgebraObject(evt)) { //adviseUser("But I do not think it is isOnAlgebraObject", k_adviseUserImportance_Normal); return Algebra.onMouseDefault(); } var object = Algebra.getObjectById(g_myAlgebraObject.equation, srcElement.id); if (object && Algebra.isDropBar(object)) { //report("It is a drop bar, so calling Algebra.onMouseEnterForDropBar\n"); //adviseUser("It is a drop bar, so calling Algebra.onMouseEnterForDropBar", k_adviseUserImportance_Normal); Algebra.onMouseEnterForDropBar(evt); } //report("ENTER " + srcElement.id + "\n"); return true; } Algebra.mouseCaptureSet = function() { //report("mouseCaptureSet\n"); var manipulationBox = document.getElementById("EquationManipulationBox"); if (manipulationBox != null) { manipulationBox.setCapture(); // WARNING DOes not work for Google Chrome. A table row element does not support thesetCapture method!! } else { alert("FAILED TO Algebra.mouseCaptureSet()"); reportNullObject("Algebra.mouseCaptureSet()","manipulationBox"); } } Algebra.mouseCaptureRelease = function() { //report("mouseCaptureRelease\n"); var manipulationBox = document.getElementById("EquationManipulationBox"); if (manipulationBox != null) { manipulationBox.releaseCapture(); // WARNING Does not work for Google Chrome. A table row element does not support thesetCapture method!! } } /////////////////////////////////////////////////////////////////// // // ON MOUSE DOWN // /////////////////////////////////////////////////////////////////// var g_mouseDownObjectId = null; // Indicates which object the mouse was on when the // mousedoen occurred. Used in onMouseMove when we determine it's a drag. Algebra.onMouseDown = function(evt) { if (g_isInStepBack) return false; var evt = evt || window.event; if (!evt) { //report("DOWN\n"); //report("NO EVENT\n"); return false; } if (!Algebra.isOnAlgebraObject(evt)) { //report("DOWN\n"); //report("NOT ON AN ALGEBRA OBJECT\n"); return Algebra.onMouseDefault(); } //report("FROM Algebra.onMouseDown TO Algebra.clearMouseParameters()\n"); Algebra.clearMouseParameters(); //report("DOWN\n"); var srcElement = evt.target || evt.srcElement; g_mouseDownObjectId = srcElement.id; //report("g_mouseDownObjectId = " + g_mouseDownObjectId + "\n"); g_isMouseButtonDown = true; Algebra.mouseCaptureSet(); } /////////////////////////////////////////////////////////////////// // // ON MOUSE MOVE // /////////////////////////////////////////////////////////////////// //var mousePositionAtDragStart; // These variables hold the HTML code that gets loaded into the innerHTML of the dragItem // so that when the dragItem gets moved around the contained (being dragged) objects get // dragged along with it. The "Source" version corresponds to what the objects should look like // when they are being dragged around within the side of the equation that they "came from". // The "Target" version corresponds to what the objects should look like when they are being // dragged around within the side of the equation that they are "going to". // Note that the Target version MAY not be applicable, depending on the type of drag that is // ocurring. Only in those cases where it makes sense for the objects to be moved over to // the other side will the Target version actually have its HTML code. var g_dragItemHTMLCodeForSideSource = ""; var g_dragItemHTMLCodeForSideTarget = ""; var g_dragItemHTMLCodeForSideTargetLower = ""; Algebra.insertObjectInArray = function(object, objects, insertionIndex) { // NOTE that this does NOT take into account what kind of obejcts are already within the array. // This is just a brute force insertion of whatever is specified and inserted at the specified location. //report("IN Algebra.insertObjectInArray with "+objects.length+" objects:\n"); if (!object) { reportNullObject("Algebra.insertObjectInArray(object, objects, insertionIndex)","object"); return null; } if (!objects || !objects.length || (objects.length < 1)) { reportNullObject("Algebra.insertObjectInArray(object, objects, insertionIndex)","objects"); return null; } if ((insertionIndex < 0) || (insertionIndex > objects.length)) { reportProgrammingError("Algebra.insertObjectInArray(object, objects, index)","invalid insertionIndex"); return null; } var newObjects = new Array(); // Copy over the objects BEFORE the insertionIndex. for (var ndx = 0 ; ndx < insertionIndex ; ++ndx) { newObjects.push(objects[ndx]); } // Insert the new object. newObjects.push(object); // Copy over the objects AFER the insertionIndex. for (var ndx = insertionIndex ; ndx < objects.length ; ++ndx) { newObjects.push(objects[ndx]); } //report("RETURNING FROM Algebra.insertObjectInArray with "+newObjects.length+" objects:\n"); return newObjects; } Algebra.generateSourceAndTargetHTMLObjectsForMultiplyObjects = function(objects) { //report("IN Algebra.generateSourceAndTargetHTMLObjectsForMultiplyObjects(objects)\n"); // Now go through the objects and compose the composite HTML that will // fill the dragItem. var positionLeft = 0; var positionTop; var algebraObject; var heightMaximum = 0; var height; g_dragItemHTMLCodeForSideSource = ""; g_dragItemHTMLCodeForSideTarget = ""; g_dragItemHTMLCodeForSideTargetLower = ""; var numberOfObjects = 0; // // DETERMINE MAX HEIGHT // // Go through the objects and determine their maximum height. //reportClear(); //report("Entering loop with "+objects.length+" objects to calculate max height\n"); for (var index = 0 ; index < objects.length ; ++index) { algebraObject = objects[index]; if (!algebraObject || (algebraObject == null)) { reportNullObject("generateSourceAndTargetHTMLObjectsForMultiplyObjects(objects)","algebraObject"); return; } height = algebraObject.getHeight(false); if (height > heightMaximum) { heightMaximum = height; } } ///report("Done getting maximum height = "+heightMaximum+"\n"); var centerlineVerticalForSource = heightMaximum / 2; var divisionLine = new Algebra.OperatorDivisionLine(objects[0].parentObject, objects[0].parentObjectId); var divisionLineHeight = divisionLine.getHeight(); var centerlineVerticalForTarget = centerlineVerticalForSource + divisionLineHeight + k_interDivisionSpaceHeight; var widthTotalForTarget = 0; //report("Composing HTML text for "+objects.length+" objects\n"); for (var index = 0 ; index < objects.length ; ++index) { //report("Looking at object #"+index+"\n"); algebraObject = objects[index]; //report("Is a selected object\n"); ++numberOfObjects; // SOURCE // vertically center... height = algebraObject.getHeight(false); positionTop = centerlineVerticalForSource - (height / 2); //report("About to create HTML code for this object\n"); var objectHTML = algebraObject.toHTML(positionLeft, positionTop, k_displayStateSelected); //report("Back from creating HTML code for this object\n"); g_dragItemHTMLCodeForSideSource += objectHTML; // TARGET height = algebraObject.getHeight(false); positionTop = centerlineVerticalForTarget - (height / 2) var objectHTML = algebraObject.toHTML(positionLeft, positionTop, k_displayStateSelected); g_dragItemHTMLCodeForSideTarget += objectHTML; positionLeft += algebraObject.getWidth(false); widthTotalForTarget += algebraObject.getWidth(false); if (!Algebra.isNonOperator(algebraObject)) { positionLeft += k_interObjectSpaceWidth; widthTotalForTarget += k_interObjectSpaceWidth; } } // Add the division line to the target. divisionLine.width = widthTotalForTarget; divisionLine.height = divisionLineHeight; // Not sure why I need to do this, but it seems to shrink to 1 in height if I don't. g_dragItemHTMLCodeForSideTarget += divisionLine.toHTML(0,0, k_displayStateTarget); //report("divisionLine.toHTML(0,0, k_displayStateTarget); = \n"+divisionLine.toHTML(0,0, k_displayStateTarget)+"\n"); } Algebra.generateSourceAndTargetHTMLObjectsForAddSubtractObjects = function(objects) { //report("IN Algebra.generateSourceAndTargetHTMLObjectsForAddSubtractObjects(objects)\n"); if (!objects || (objects == null) || !objects.length) { reportNullObject("Algebra.generateSourceAndTargetHTMLObjectsForAddSubtractObjects(objects)","objects"); return false; } // Now go through the objects and compose the composite HTML that will // fill the dragItem. var positionLeft = 0; var positionTop; var algebraObject; var heightMaximum = 0; var height; g_dragItemHTMLCodeForSideSource = ""; g_dragItemHTMLCodeForSideTarget = ""; g_dragItemHTMLCodeForSideTargetLower = ""; var numberOfObjects = 0; // // INSERT LEADING PLUS IF NEEDED // // Make sure first object is a plus or minus. I.e. if the first object // is neither a plus nor a minus, insert a plus sign. //report("objects[0] = "+objects[0].className+"\n"); if (!Algebra.isOperatorAddOrSubtract(objects[0])) { //report("TO Algebra.insertObjectInArray\n"); var objectPlus = new Algebra.OperatorPlus(objects[0].parentObject, objects[0].parentObjectId); objects = Algebra.insertObjectInArray(objectPlus, objects, 0); } // // DETERMINE MAX HEIGHT // // Go through the objects and determine their maximum height. //reportClear(); //report("Entering loop with "+objects.length+" objects to calculate max height\n"); for (var index = 0 ; index < objects.length ; ++index) { algebraObject = objects[index]; if (!algebraObject || (algebraObject == null)) { reportNullObject("Algebra.generateSourceAndTargetHTMLObjectsForAddSubtractObjects((objects))","algebraObject"); return false; } height = algebraObject.getHeight(false); if (height > heightMaximum) { heightMaximum = height; } } ///report("Done getting maximum height = "+heightMaximum+"\n"); var centerlineVertical = heightMaximum / 2; //report("Composing HTML text for "+objects.length+" objects\n"); for (var index = 0 ; index < objects.length ; ++index) { //report("Looking at object #"+index+"\n"); algebraObject = objects[index]; //report("Is a selected object\n"); ++numberOfObjects; ////////// // // SOURCE // ////////// // vertically center... height = algebraObject.getHeight(false); positionTop = centerlineVertical - (height / 2); //report("About to create HTML code for this object\n"); var objectHTML = algebraObject.toHTML(positionLeft, positionTop, k_displayStateSelected); //report("Back from creating HTML code for this object\n"); g_dragItemHTMLCodeForSideSource += objectHTML; ////////// // // TARGET // ////////// // Where we "flip" plus and minus signs. // NOTE WARNING How do we detect when there is no leading plus or minus sign // implying that we should ADD an implicit plus sign? // NOTE WARNING How do we detect that we are handling a numerator or denominator, // which needs special handling? // Right now the numerator and denominator objects are merely GroupSequenceAddSubtract type // objects. Maybe I need to create specific Numerator and Denominator objects! ??? var displayState = k_displayStateSelected; if (algebraObject instanceof Algebra.OperatorPlus) { algebraObject = new Algebra.OperatorMinus(null, k_dummyParentId); displayState = k_displayStateTarget; } else if (algebraObject instanceof Algebra.OperatorMinus) { algebraObject = new Algebra.OperatorPlus(null, k_dummyParentId); displayState = k_displayStateTarget; } height = algebraObject.getHeight(false); positionTop = centerlineVertical - (height / 2); var objectHTML = algebraObject.toHTML(positionLeft, positionTop, displayState); g_dragItemHTMLCodeForSideTarget += objectHTML; positionLeft += algebraObject.getWidth(false); if (!Algebra.isNonOperator(algebraObject)) { positionLeft += k_interObjectSpaceWidth; } } //////////////// // // TARGET LOWER // //////////////// // We create a Target Lower version of the drag item ONLY under the following circumstance: // 1. The Drag Item is an add/subtract type of item. // 2. We are dragging the ENTIRE source side. // // This means that when the user drags the drag item to the target side it is possible that // the user is intending the add or subtract the object to/from theother side // OR hes intending tomake it a denomninator of the other side. // By default we assume he's going to do an add or subtract, and we show the g_dragItemHTMLCodeForSideTarget. // But if he drags the drag item "sufficiently beneath" the target expression we then show // the g_dragItemHTMLCodeForSideTargetLower, which we create here as being the g_dragItemHTMLCodeForSideSource // (not the "Source" part of that) but with a DivisionLine above it. // We already know that he Drag Item is an add/subtract type of item, since we're in the // Algebra.generateSourceAndTargetHTMLObjectsForAddSubtractObjects function. //report("Do we need to cretae a Target Lower?"); if (g_isDraggingEntireSideOfEquation) { //report("YES, creating Target Lower."); // Then we need to create that extra dragItem. positionLeft = 0; var divisionLine = new Algebra.OperatorDivisionLine(objects[0].parentObject, objects[0].parentObjectId); var divisionLineHeight = divisionLine.getHeight(); //report("divisionLineHeight = "+divisionLineHeight+"\n"); //report("centerlineVertical (for non-lower dragItem) = "+centerlineVertical+"\n"); var centerlineVerticalForTargetLower = heightMaximum / 2 + divisionLineHeight + k_interDivisionSpaceHeight; //report("centerlineVerticalForTargetLower = "+centerlineVerticalForTargetLower+"\n"); var widthTotalForTargetLower = 0; //report("Composing HTML text for "+objects.length+" objects\n"); for (var index = 0 ; index < objects.length ; ++index) { //report("Looking at object #"+index+"\n"); algebraObject = objects[index]; //report("Is a selected object\n"); // TARGET LOWER // vertically center... height = algebraObject.getHeight(false); positionTop = centerlineVerticalForTargetLower - (height / 2); //report("About to create HTML code for this object\n"); var objectHTML = algebraObject.toHTML(positionLeft, positionTop, k_displayStateSelected); //report("Back from creating HTML code for this object\n"); g_dragItemHTMLCodeForSideTargetLower += objectHTML; widthTotalForTargetLower += algebraObject.getWidth(false); positionLeft += algebraObject.getWidth(false); if (!Algebra.isNonOperator(algebraObject)) { positionLeft += k_interObjectSpaceWidth; widthTotalForTargetLower += k_interObjectSpaceWidth; } } //report("widthTotalForTargetLower = "+widthTotalForTargetLower+"\n"); // Add the division line to the target. divisionLine.width = widthTotalForTargetLower; divisionLine.height = divisionLineHeight; // Not sure why I need to do this, but it seems to shrink to 1 in height if I don't. g_dragItemHTMLCodeForSideTargetLower += divisionLine.toHTML(0,0, k_displayStateTarget); //report("Created g_dragItemHTMLCodeForSideTargetLower\n"); } return true; } var g_areDragObjectsAddSubtractObjects = false; var g_isDragObjectFractionPart = false; var g_dragObjectFractionPart = k_fractionPartNone; var g_isDragObjectATopLevelObject = false; var g_isFractionPartWithinSoleObjectOnSide = false; var g_isDraggingEntireSideOfEquation = false; var g_areAllNeighborsSelected = false; var g_dragObjectFractionPartIsWithinMultiplyGroup = false; var g_canDropOnOtherSideOfEquation = false; var g_canOnlyDropWithinSourceGroup = false; // DROP TRAGET INFO var g_dropTargetExpressionParent = null; var g_isDropTargetTopLevel = false; // Drop target FRACTION Info var g_isDropTargetWithinFraction = false; var g_isDropTargetWithinTopLevelFraction = false; var g_isDropTargetWithinSoleTopLevelFraction = false; var g_isDropTargetWithinNumeratorOrDenominator = false; var g_isDropTargetWithinNumerator = false; var g_isDropTargetWithinDenominator =false; var g_isDropTargetATopObjectWithinNumeratorOrDenominator = false; var g_dropTargetNumeratorOrDenominatorParent = null; // null if drop target is NOT within a numerator or denominator var g_dropTargetFraction = null; // null if g_dropTargetNumeratorOrDenominatorParent is null var g_mousePositionMostRecent = null; Algebra.onMouseMove = function(evt) { //adviseUser("IN Algebra.onMouseMove\n", k_adviseUserImportance_Normal); if (g_isInStepBack) return false; var evt = evt || window.event; if (!evt) { //adviseUser("IN Algebra.onMouseMove NO evt\n", k_adviseUserImportance_Normal); return false; } var srcElement = evt.target || evt.srcElement; // RCG NEW APPROACH for snap back g_mousePositionMostRecent = getMousePositionRelativeToContainer(evt, "EquationManipulationBox"); if (!Algebra.isOnAlgebraObject(evt)) { //adviseUser("IN Algebra.onMouseMove NOT ON ALGEBRA OBJECT\n", k_adviseUserImportance_Normal); //report("MOVE: "+ srcElement.id + " NOT ON ALGEBRA OBJECT\n"); return Algebra.onMouseDefault(); } if (g_isMouseButtonDown) { //report("Mouse button is DOWN\n"); ++g_numberOfMouseMovesBetweenMouseDownAndUp; //report("g_numberOfMouseMovesBetweenMouseDownAndUp = " + g_numberOfMouseMovesBetweenMouseDownAndUp + "\n"); if (g_numberOfMouseMovesBetweenMouseDownAndUp > k_minimumNumberOfMovesBeforeDrag) { //report("g_numberOfMouseMovesBetweenMouseDownAndUp > k_minimumNumberOfMovesBeforeDrag\n"); if (g_isDragging == false) { // NOT (YET) DRAGGING if (g_mouseDownObjectId.length != 0) { //report("Not yet dragging AND we have a g_mouseDownObjectId = "+g_mouseDownObjectId+"\n"); // NOTE Actually need to detect whether we have a selected item to drag!!!! // and are actually ON one of the selected items!!! var object = Algebra.getObjectById(g_myAlgebraObject.equation, g_mouseDownObjectId); if (!object) { //report("Object not located by Algebra.getObjectById(" + srcElement.id + ")\n"); // We did not find the object. But that may be OK, since this onmouseup handler // is associated with the overall Equation Manipulation Box, so the mouse could // just be over the box or some other non-Algebra object. //report("Did not find Algebra object for " + g_mouseDownObjectId + "\n"); return false; // Cancel event. } //report("Found Algebra object for " + g_mouseDownObjectId + "\n"); // This sets the g_dragItemSourceSide variable. //report("About to Algebra.determineDragItemSourceSide\n"); Algebra.determineDragItemSourceSide(g_mouseDownObjectId); //report("BACK FROM Algebra.determineDragItemSourceSide\n"); object = Algebra.getRealObject(object); //report("The real object is " + object.className + " = " + object.iD + "\n"); if (!object.currentDisplayState) { // The object does not even have a currentDisplayState property, // so we can't do anything with it. // It MAY be a drop bar. if (Algebra.isDropBar(object)) { // Then see if there is a neighbor object that's an actual object and try to use // that object as the clicked-on object. var neighborLeft = Algebra.getNeighbor(object.iD, k_neighborDirectionLeft); if (neighborLeft && (neighborLeft != null) && neighborLeft.currentDisplayState) { object = neighborLeft; } else { var neighborRight = Algebra.getNeighbor(object.iD, k_neighborDirectionRight); if (neighborRight && (neighborRight != null) && neighborRight.currentDisplayState) { object = neighborRight; } else { // No neighbors that could be used. //report("But Algebra Object " + object.className + " = " + object.iD + " is a drop bar with no neighbors that can be auto-selected.\n"); //alert("But Algebra Object " + object.className + " = " + object.iD + " is a drop bar with no neighbors that can be auto-selected\n"); return false; } } } else { // No currentDisplayState and not a drop bar. What the heck IS this thing??? //report("Algebra Object " + object.className + " = " + object.iD + " has no currentDisplayState and is not a drop bar. Don't know how to handle this thing.\n"); //alert("Algebra Object " + object.className + " = " + object.iD + " has no currentDisplayState and is not a drop bar. Don't know how to handle this thing\n"); } } if (object.currentDisplayState == k_displayStateNormal) { //report("Not yet currentDisplayState. Going to Algebra.handleClickOnObject\n"); // The objects is NOT YET selected. See if we can make it selected, // and appropriately de-select any non-related objects. //report("About to Algebra.handleClickOnObject\n"); Algebra.handleClickOnObject(object); //report("BACK FROM Algebra.handleClickOnObject\n"); } if (object.currentDisplayState == k_displayStateSelected) { //report("Algebra object is now in selected state.\n"); if (!g_mySelectedObjects.includes(object.iD)) { // Needed in case we need to snap it back to where it came from. object.positionRelativeToContainer = getHTMLObjectPositionRelativeToContainer("EquationManipulationBox", object.iD); g_mySelectedObjects.add(object.iD, object); } Algebra.initializeSideSwitch(g_mouseDownObjectId); g_mouseDownObjectId = null; // We now have a valid selected object that we can drag. //////////////////////// // // START DRAG // //////////////////////// reportClear(); //report("DRAG START\n"); //adviseUser("Starting Drag", k_adviseUserImportance_Normal); g_mySelectedObjects.report(); g_isDragging = true; //report("g_isDragging = true;\n"); g_areDragObjectsAddSubtractObjects = false; g_areDragObjectsMultiplyObjects = false; g_isDragObjectFractionPart = false; g_isDraggingEntireSideOfEquation = false; g_isDragObjectATopLevelObject = false; g_isFractionPartWithinSoleObjectOnSide = false; g_areAllNeighborsSelected = false; g_dragObjectFractionPartIsWithinMultiplyGroup = false; g_areAllNumeratorChildrenSelected = false; g_areAllDenominatorChildrenSelected = false; g_isEntireNumeratorOrDenominatorSelected = false; g_canDropOnOtherSideOfEquation = false; g_canOnlyDropWithinSourceGroup = false; g_dragObjectNumeratorOrDenominator = null; g_dragObjectIsInNumeratorOrDenominator = false; g_dragObjectIsInNumerator = false; g_dragObjectIsInDenominator = false; g_dragObjectIsATopObjectInNumeratorOrDenominator = false; reportTruth("isDragObjectATopLevelObject",g_isDragObjectATopLevelObject); reportTruth("areDragObjectsAddSubtractObjects",g_areDragObjectsAddSubtractObjects); reportTruth("areDragObjectsMultiplyObjects",g_areDragObjectsMultiplyObjects); reportTruth("isDragObjectFractionPart",g_isDragObjectFractionPart); reportTruth("isDraggingEntireSideOfEquation",g_isDraggingEntireSideOfEquation); reportTruth("isFractionPartWithinSoleObjectOnSide",g_isFractionPartWithinSoleObjectOnSide); reportTruth("areAllNeighborsSelected",g_areAllNeighborsSelected); reportTruth("areAllNeighborsSelected",g_areAllNeighborsSelected); reportTruth("dragObjectFractionPartIsWithinMultiplyGroup",g_dragObjectFractionPartIsWithinMultiplyGroup); reportTruth("dragObjectIsInNumeratorOrDenominator",g_dragObjectIsInNumeratorOrDenominator); reportTruth("dragObjectIsInNumerator",g_dragObjectIsInNumerator); reportTruth("dragObjectIsInDenominator",g_dragObjectIsInNumerator); reportTruth("dragObjectIsATopObjectInNumeratorOrDenominator",g_dragObjectIsATopObjectInNumeratorOrDenominator); reportTruth("areAllDenominatorChildrenSelected",g_areAllDenominatorChildrenSelected); reportTruth("canDropOnOtherSideOfEquation",g_canDropOnOtherSideOfEquation); reportTruth("canOnlyDropWithinSourceGroup",g_canOnlyDropWithinSourceGroup); g_isDraggingEntireSideOfEquation = Algebra.isDraggingEntireSideOfEquation(); reportTruth("isDraggingEntireSideOfEquation", g_isDraggingEntireSideOfEquation); g_isDragObjectATopLevelObject = Algebra.isDragObjectATopLevelObject(); reportTruth("isDragObjectATopLevelObject",g_isDragObjectATopLevelObject); g_dragItemHTMLCodeForSideSource = ""; g_dragItemHTMLCodeForSideTarget = ""; // The objects that we are dragging reside in the g_mySelectedObjects array. // Copy the HTML text of each of the items into the dragItem HTML object. var dragItem = document.getElementById("DragItem"); if (dragItem == null) { reportProgrammingError("Algebra.onMouseMove","FAILED TO document.getElementById(dragItem)"); returnfalse; } // Actually, I think we need to go through the PARENT GROUP object // and only select the ones that are SELECTED. That way we get them // in the actual order in which they APPEAR, rather than the order in // which they reside in the g_mySelectedObjects. var objects; var objectGroupParent; objectGroupParent = Algebra.getObjectById(g_myAlgebraObject.equation, g_algebraPreviouslySelectedObjectGroupParentId); if (!objectGroupParent || objectGroupParent == null) { reportNullObject("Algebra.onMouseMove","objectGroupParent"); return; } if (!objectGroupParent.objects || objectGroupParent.objects == null) { reportNullObject("Algebra.onMouseMove","objectGroupParent.objects"); return; } objects = objectGroupParent.objects; if (!objects || objects == null) { reportNullObject("Algebra.onMouseMove","objects"); return; } // Re-generate the g_mySelectedObjects so that it is // in the order in which the objects appear within their parent's group. // AND CREATE another array holding just the selected objects, which we can pass to // the appropriate handler to create the dragItem HTML stuff. //////////////////////////////////////////////////////////////////////////////////// // // RE-BUILD ARRAY in CORRECT ORDER and DETERMINE IF ALL NEIGHBORS ARE SELECTED // ///////////////////////////////////////////////////////////////////////////////////// // var objectsForDragItem = new Array(); g_mySelectedObjects.empty(); //report("First pass through "+objects.length+" objects (from parent group\n"); //report("Generating objectsForDragItem\n"); g_areAllNeighborsSelected = true; //reportTabIn(); for (var index = 0 ; index < objects.length ; ++index) { algebraObject = objects[index]; // Only check the non-drop bars !! //report("Looking at object #"+index+" "+algebraObject.className+"\n"); if (!Algebra.isDropBar(algebraObject)) { //report("Not a drop bar.\n"); if (algebraObject.currentDisplayState == k_displayStateSelected) { //report("Is a selected object, pushing on to objectsForDragItem.\n"); objectsForDragItem.push(algebraObject); // Needed in case we need to snap it back to where it came from. algebraObject.positionRelativeToContainer = getHTMLObjectPositionRelativeToContainer("EquationManipulationBox", algebraObject.iD); // RCG NEW APPROACH to snap back g_dragStartPosition = algebraObject.positionRelativeToContainer; g_mySelectedObjects.add(algebraObject.iD, algebraObject); } else { //report("NOT a selected object\n"); g_areAllNeighborsSelected = false; } } else { //report("Is a drop bar, so ignoring.\n"); } } //reportTabOut(); //report("AFTER REDBUILD OF g_mySelectedObjects\n"); //g_mySelectedObjects.report(); //report("Done first pass. objectsForDragItem has "+objectsForDragItem.length+" objects.\n"); if (objectsForDragItem.length == 0) { reportProgrammingError("Algebra.onMouseMove","NO objectsForDragItem??"); return; } var oneOfTheSelectedObjects = g_mySelectedObjects.getObject(0); reportTruth("areAllNeighborsSelected",g_areAllNeighborsSelected); g_isFractionPartWithinSoleObjectOnSide = false; /////////////////////////////////////////////////// // // DETERMINE ADD/SUBTRACT or MULTIPLY OBJECTS // /////////////////////////////////////////////////// // Determine whether these are add/subtract objects or multiply objects. //report("Determine whether these are add/subtract objects or multiply objects.\n"); g_areDragObjectsAddSubtractObjects = false; g_areDragObjectsMultiplyObjects = false; if (Algebra.getNumberOfAlgebraChildren(objectGroupParent) == 1) // YES, we want to look at objects here (which are the // group of objects within which our draggable/selected objects reside, // NOT objectsForDragItem, which are ONLY the ones selected. // We need to do this because it's possible that only ONE object is // selected, yet it's within a multiply group. We do not want to // mistake it for an addition object. { //report("Only one object, so we assume it's a plus type of object.\n"); // Only one object, so we assume it's a "plus" type of object. g_areDragObjectsAddSubtractObjects = true; } else { //report("We have multiple objects.\n"); // We have multiple objects. //report("Second pass through "+objects.length+" objects (from parent group\n"); //reportTabIn(); // YES Go through objects and NOT mySelectedObjects since mySelectedObjects MIGHT NOT // have the necessary multiply or add/subtract operators to determine whether it's // multiply or add/subtract!!!. for (var index = 0 ; index < objects.length ; ++index) { algebraObject = objects[index]; //report("Looking at object #"+index+" "+algebraObject.className+"\n"); // In this case we do NOT want to only check those that are selected. // Rather, we are trying to determine whether we're dealing with add/sub // or multiply. // Actually, it might be easier just to look at the enclosing group, // although it's possible that we have an enclosing group that is, for exampe, // just the LeftSide or a Parentheses, and there IS NO enclosing GroupSequenceAddSubtract // or GroupSequenceMultiply. So this approach is probably best. if (Algebra.isOperatorMultiply(algebraObject)) { //report("areMultiplyObjects\n"); g_areDragObjectsMultiplyObjects = true; break; } else if (Algebra.isOperatorAddOrSubtract(algebraObject)) { //report("areAddSubtractObjects\n"); g_areDragObjectsAddSubtractObjects = true; break; } else { //report("Neither add, subtract nor multiply\n"); } } //reportTabOut(); } reportTruth("areDragObjectsMultiplyObjects",g_areDragObjectsMultiplyObjects); reportTruth("areDragObjectsAddSubtractObjects",g_areDragObjectsAddSubtractObjects); ///////////////////////////////////////////////////////// // // DETERMINE NUMERATOR OR DENOMINATOR INFO // ///////////////////////////////////////////////////////// report("DETERMINE NUMERATOR OR DENOMINATOR INFO\n"); // It is only a numerator or denominator object if // 1. It is WITHIN a numerator or denominator object. // AND // 2. It is a TOP LEVEL object in the numerator or denominator. // AND EITHER // 3.A All of its neighbors are also selected, i.e. the ENTIRE numerator or // denominator is selected. // OR // 3.B It is within a MULTIPLY group. // // selected AND it has a numerator or denominator parent (with ONLY intervening // group parentheses [ or groups with just ONE child] between it and the numerator or denominator parent). g_dragObjectFractionPart = k_fractionPartNone; g_isDragObjectFractionPart = false; report("Retrieving g_dragObjectNumeratorOrDenominatorGroup (if available)\n"); reportTabIn(); g_dragObjectNumeratorOrDenominatorGroup = Algebra.getNumeratorOrDenominatorParent(oneOfTheSelectedObjects); if (g_dragObjectNumeratorOrDenominatorGroup && (g_dragObjectNumeratorOrDenominatorGroup != null)) { report("g_dragObjectNumeratorOrDenominatorGroup IS available\n"); reportTabIn(); if (g_dragObjectNumeratorOrDenominatorGroup instanceof Algebra.GroupNumerator) { report("IS NUMERATOR\n"); g_dragObjectIsInNumeratorOrDenominator = true; g_dragObjectIsInNumerator = true; } else if (g_dragObjectNumeratorOrDenominatorGroup instanceof Algebra.GroupDenominator) { report("IS DENOMINATOR\n"); g_dragObjectIsInNumeratorOrDenominator = true; g_dragObjectIsInDenominator = true; } reportTabOut(); } else { report("NOT AVAILBALE\n"); } reportTabOut(); reportTruth("dragObjectIsInNumeratorOrDenominator",g_dragObjectIsInNumeratorOrDenominator); reportTruth("dragObjectIsInNumerator",g_dragObjectIsInNumerator); reportTruth("dragObjectIsInDenominator",g_dragObjectIsInDenominator); if (g_dragObjectIsInNumeratorOrDenominator) { g_isDragObjectFractionPart = true; var parentThing = Algebra.getFirstGroupWithContentsDownTheHierarchy(g_dragObjectNumeratorOrDenominatorGroup); if (!parentThing || (parentThing == null)) { reportNullObject("","parentThing"); return; } //report(" determining g_dragObjectIsATopObjectInNumeratorOrDenominator\n"); g_dragObjectIsATopObjectInNumeratorOrDenominator = Algebra.isChildOf(parentThing, oneOfTheSelectedObjects); if (g_dragObjectIsATopObjectInNumeratorOrDenominator) { reportTruth("dragObjectIsATopObjectInNumeratorOrDenominator",g_dragObjectIsATopObjectInNumeratorOrDenominator); // OK. So we know that the drag object is within a numerator or denominator and that // it is a top object within the numerator or denominator. // If all of its neighbors are also selected OR if it is within a multiply group // then it is a bona fide dragging fraction part. if (g_areAllNeighborsSelected || g_areDragObjectsMultiplyObjects) { reportTruth("isDragObjectFractionPart",g_isDragObjectFractionPart); g_dragObjectFractionPart = Algebra.determineFractionPart(oneOfTheSelectedObjects); if ((g_dragObjectFractionPart == k_fractionPartNumerator) || (g_dragObjectFractionPart == k_fractionPartDenominator)) { if (g_areAllNeighborsSelected) { // ENTIRE NUMERATOR or DENOMINATOR is SELECTED. // Which can mean entire ADD/SUBTRACT group or enitre MULTIPLY group. if (g_dragObjectFractionPart == k_fractionPartNumerator) { g_areAllNumeratorChildrenSelected = true; g_isEntireNumeratorOrDenominatorSelected = true; reportTruth("areAllNumeratorChildrenSelected",g_areAllNumeratorChildrenSelected); } else if (g_dragObjectFractionPart == k_fractionPartDenominator) { g_areAllDenominatorChildrenSelected = true g_isEntireNumeratorOrDenominatorSelected = true;; reportTruth("areAllDenominatorChildrenSelected",g_areAllDenominatorChildrenSelected); } } // It is NOT the ENTIRE part. When it's only a subset of the numerator or denominator // then it MUST be of a MULTPLY group. You cannot drag out a subset of an ADD/SUBTRACT group. else if (g_areDragObjectsMultiplyObjects) { g_dragObjectFractionPartIsWithinMultiplyGroup = true; reportTruth("dragObjectFractionPartIsWithinMultiplyGroup",g_dragObjectFractionPartIsWithinMultiplyGroup); } } else { reportProgrammingError("Algebra.onMouseMove","Thought we had numerator or denominator, but could not determine what it is."); return; } var divisionObject = Algebra.getObjectById(g_myAlgebraObject.equation, g_dragObjectNumeratorOrDenominatorGroup.parentObjectId); if (!divisionObject || (divisionObject == null)) { reportNullObject("Algebra.onMouseMove","divisionObject"); return; } g_isFractionPartWithinSoleObjectOnSide = Algebra.isObjectSoleObjectOnSide(divisionObject); reportTruth("isFractionPartWithinSoleObjectOnSide",g_isFractionPartWithinSoleObjectOnSide); } } } //////////////////////////////////////////// // // DETERMINE OTHER SIDE DROPABILITY // //////////////////////////////////////////// // We can only move a fraction part to the other side if it is within a fraction that is // the ONLY object on the side. if (g_isDraggingEntireSideOfEquation) { g_canDropOnOtherSideOfEquation = true; } else if (g_isFractionPartWithinSoleObjectOnSide) { // Yup, our fraction part is the only object on its side. // But we can only move the fraction part to the other side if it is the ENTIRE numerator or ENTIRE denominator. if (g_areAllNumeratorChildrenSelected || g_areAllDenominatorChildrenSelected) { g_canDropOnOtherSideOfEquation = true; } else { // OR, if the fraction part is not the ENTIRE numerator or ENTIRE denominator, then we can // only move the part to the other side if it is a part of a Multiply group. g_canDropOnOtherSideOfEquation = g_areDragObjectsMultiplyObjects; } } else { // We did not meet the fraction part drop criteria, but maybe it's not a fraction part. // In this case we an move the drag object to the other side if the drag object itself is a top-level object. g_canDropOnOtherSideOfEquation = g_isDragObjectATopLevelObject; } reportTruth("canDropOnOtherSideOfEquation",g_canDropOnOtherSideOfEquation); g_canOnlyDropWithinSourceGroup = !g_canDropOnOtherSideOfEquation; reportTruth("canOnlyDropWithinSourceGroup",g_canOnlyDropWithinSourceGroup); //////////////////////////////////// // // HANDLE OBJECTS APPROPRIATELY // //////////////////////////////////// if (g_dragObjectFractionPart == k_fractionPartNumerator) { // NUMERATOR //report("TO Algebra.handleFractionPartNumerator(objects);\n"); Algebra.generateSourceAndTargetHTMLObjectsForFractionPartNumerator(objectsForDragItem, g_areDragObjectsMultiplyObjects); if (generatedOK == false) { reportProgrammingError("Algebra.onMouseMove","Failed to Algebra.generateSourceAndTargetHTMLObjectsForFractionPartNumerator(objectsForDragItem, g_areDragObjectsMultiplyObjects)"); } } else if (g_dragObjectFractionPart == k_fractionPartDenominator) { // DENOMINATOR //report("TO Algebra.handleFractionPartDenominator(objects);\n"); var generatedOK = Algebra.generateSourceAndTargetHTMLObjectsForFractionPartDenominator(objectsForDragItem, g_areDragObjectsMultiplyObjects); if (generatedOK == false) { reportProgrammingError("Algebra.onMouseMove","Failed to Algebra.generateSourceAndTargetHTMLObjectsForFractionPartDenominator(objectsForDragItem, g_areDragObjectsMultiplyObjects)"); } } else if (g_areDragObjectsMultiplyObjects) { // MULTIPLY //report("TO Algebra.generateSourceAndTargetHTMLObjectsForMultiplyObjects(objects);\n"); var generatedOK = Algebra.generateSourceAndTargetHTMLObjectsForMultiplyObjects(objectsForDragItem); if (generatedOK == false) { reportProgrammingError("Algebra.onMouseMove","Failed to Algebra.generateSourceAndTargetHTMLObjectsForMultiplyObjects(objectsForDragItem)"); } } else if (g_areDragObjectsAddSubtractObjects) { // ADD or SUBTRACT //report("TO Algebra.generateSourceAndTargetHTMLObjectsForAddSubtractObjects(objects);\n"); var generatedOK = Algebra.generateSourceAndTargetHTMLObjectsForAddSubtractObjects(objectsForDragItem); if (generatedOK == false) { reportProgrammingError("Algebra.onMouseMove","Failed to Algebra.generateSourceAndTargetHTMLObjectsForAddSubtractObjects(objectsForDragItem)"); } } else { reportProgrammingError("Algebra.onMouseMove(evt)","Could not determine how to handle the selected objects."); return false; } //report("Setting dragItem.innerHTML = g_dragItemHTMLCodeForSideSource\n"); // Momentarilly hide the dragItem so that it does not pop up at some weird location. dragItem.style.visibility = "hidden"; dragItem.innerHTML = g_dragItemHTMLCodeForSideSource; //report("DONE Setting dragItem.innerHTML = g_dragItemHTMLCodeForSideSource\n"); // TURN OFF ORIGINAL OBJECTS // Now go through the g_mySelectedObjects and turn each one off (make not visible). //report("Turn off original objects\n"); //g_mySelectedObjects.report(); for (var objectId in g_mySelectedObjects.objects) { var theObject = Algebra.getObjectById(g_myAlgebraObject.equation,objectId); theObject.setDisplayState(k_displayStateHidden); // NOTE WARNING ACTUALLY, should NOT access the html object here but should do it from within // the individual setDisplayState methods. var htmlObject = document.getElementById(objectId); if (htmlObject) { htmlObject.style.visibility = "hidden"; // Also turn off neighboring dropBars. var neighborObjectLeft = Algebra.getNeighbor(objectId, k_neighborDirectionLeft); if (neighborObjectLeft instanceof Algebra.DropBarVertical) { htmlObject = document.getElementById(neighborObjectLeft.iD); htmlObject.style.visibility = "hidden"; } var neighborObjectRight = Algebra.getNeighbor(objectId, k_neighborDirectionRight); if (neighborObjectRight instanceof Algebra.DropBarVertical) { htmlObject = document.getElementById(neighborObjectRight.iD); htmlObject.style.visibility = "hidden"; } } } //report("dragItem.innerHTML =\n" + dragItem.innerHTML + "\n"); } else { // The handling of the object resulted in it NOT being a selected object. //report("The handling of the object resulted in it NOT being a selected object..\n"); } } else { // NO g_mouseDownObjectId } } else { //////////////////////// // // DRAGGING // //////////////////////// //report("DRAGGING\n"); Algebra.handleSideAndUpperLowerSwitch(evt); // WARNING NOTE: Really should NOT use explicit name of manipulation box. That should // be somehow passed into this code. var mousePositionRelativeToContainer = getMousePositionRelativeToContainer(evt, "EquationManipulationBox"); g_dragPath.push(mousePositionRelativeToContainer); var dragItem = document.getElementById("DragItem"); dragItem.style.top = mousePositionRelativeToContainer.top; // We add a little bit so that it is not precisely at the mouse position. dragItem.style.left = mousePositionRelativeToContainer.left + 15; // Make sure it is visible. It would have been made hidden when initially filled with it innerHTML // but did not yet have a valid position. dragItem.style.visibility = "visible"; //adviseUser("mouse @ "+mousePositionRelativeToContainer.left+","+mousePositionRelativeToContainer.top+" dragItem @ "+dragItem.style.left+","+dragItem.style.top+"\n", k_adviseUserImportance_Normal); } //report("EXITing from onMouseMove\n"); return false; // Cancel event. } } else { //report("Mouse button NOT DOWN during MOVE\n"); } //report("EXITing from onMouseMove\n"); return true; } Algebra.isSpecialMultiplyObject = function(object) { // This returns true if the specifiied object: // 1. is a Group Sequence Multiply Object. // 2. Contains exactly 3 non-drop bar objects // 3. Those 3 non-drop bar objects are: // a. Integer or Common Fraction // b. Operator Multiply Implicit // c. Variable //report("IN Algebra.isSpecialMultiplyObject(object)\n"); //reportTabIn(); if (!object || (object == null)) { reportNullObject("Algebra.isSpecialMultiplyObject(object)","object"); //reportTabOut(); return false; } if (object instanceof Algebra.GroupSequenceMultiply) { //report("It IS a Algebra.GroupSequenceMultiply\n"); var objects = object.objects; var numberOfNonDropBarChildren = Algebra.getNumberOfNonDropBarChildren(object); //report("numberOfNonDropBarChildren = "+numberOfNonDropBarChildren+"\n"); if (numberOfNonDropBarChildren == 3) { //report("We have 3 objects. Checking for Integer - Implicit Multiply - Variable\n"); // We have 3 objects: an object, the OperatorMultiplyImplicit, and another object. var object0 = Algebra.getNonDropBarChild(object, 0); var object1 = Algebra.getNonDropBarChild(object, 1); var object2 = Algebra.getNonDropBarChild(object, 2); //reportTabIn(); //report("object0 = "+object0.className+"\n"); //report("object1 = "+object1.className+"\n"); //report("object2 = "+object2.className+"\n"); //reportTabOut(); var firstPartIsNumber = (object0 instanceof Algebra.Integer) || (Algebra.isCommonFraction(object0)); if (firstPartIsNumber && (object1 instanceof Algebra.OperatorMultiplyImplicit) && (object2 instanceof Algebra.Variable)) { //report("YES. It is Integer - Implicit Multiply - Variable\n"); // Then this is indeed an implicit multiplication consisting of just an Integer // followed by a Variable (e.g. 3A). //reportTabOut(); return true; } } } //reportTabOut(); return false; } Algebra.getRealObject = function(object) { // This returns the "real" object related to the specified object. // That means: // if the object is a Digit, it returns the related Integer object. // if the object is a Letter, it returns the related Variable object. // if the object is a Left or Right Parentheses, it returns the related Group Parentheses object. // if the object is a division line it returns the related division object. // AFTER getting the real object, as above, it subsequently checks for the following: // If the object is an Integer or a Variable and is part of an implicit multiplication group // consisting of JUST an Integer followed by a Variable // then it returns the implicit multiplication group. // This allows for selecting things like 6A from within X + 6A - 8 // HOWEVER!!! If the 6A kind of object is the SOLE, TOP-LEVEL object on a side // then we do NOT auto-select the whole thing, we select ONLY the item that was selected. report("IN Algebra.getRealObject(object = "+object.className + " " + object.iD + " " + object.value + "\n"); reportTabIn(); var realObject = object; var isParenthesesLeft = (object instanceof Algebra.ParenthesesLeft); var isParenthesesRight = (object instanceof Algebra.ParenthesesRight); var isDivisionLine = (object instanceof Algebra.OperatorDivisionLine); var isDigit = (object instanceof Algebra.Digit); var isLetter = (object instanceof Algebra.Letter); if (isParenthesesLeft || isParenthesesRight || isDivisionLine || isDigit || isLetter ) { report("It is a left or right parentheses, a division line, or a Digit or Letter\n"); // These items all need to affect their related group parents. var parentObjectId = object.parentObjectId; //var parentObject = Algebra.getObjectById(g_myAlgebraObject.equation, parentObjectId); var parentObject = object.parentObject; report("Parent object = " + parentObject.className + " " + parentObject.iD + "\n"); if (!parentObject) { report("PARENT NOT FOUND!!!\n"); reportTabOut(); return null; } realObject = parentObject; report("So the newlyClickedObject becomes that parent object = " + realObject.className + " " + realObject.iD + "\n"); //newlyClickedObjectIsItselfAGroupObject = true; } else { // It's some other type of object that is NOT a parentheses, division line, digit or letter. report("It's some other type of object that is NOT a parentheses, division line, digit or letter.\n"); report("Object = " + object.className + " = " + object.value + "\n"); realObject = object; } report("So far realObject = "+realObject.className+"\n"); // Check to see if the object is Integer or Variable that is part of an implicit multiplication // consisting of just an Integer followed by a variable,in which case we'll want to return the // parent implicit multiplication object as the realObject. if ((realObject instanceof Algebra.Integer) || (realObject instanceof Algebra.Variable)) { report("It IS an Integer or Variable. Checking for special implicit multiply case.\n"); //var parentObject = Algebra.getObjectById(g_myAlgebraObject.equation, realObject.parentObjectId); var parentObject = realObject.parentObject; if (!parentObject || (parentObject == null)) { reportNullObject("Algebra.getRealObject(object)","parentObject"); reportTabOut(); return realObject; } report("Got parentObject = "+parentObject.className+"\n"); if (Algebra.isSpecialMultiplyObject(parentObject)) { report("YES. It is Integer - Implicit Multiply - Variable\n"); // Then this is indeed an implicit multiplication consisting of just an Integer // followed by a variable (e.g. 3A). // BUT !! If that parent is a SOLE, TOP-LEVEL object on the side, then we do NOT // select the composite object!!! if (!Algebra.isObjectSoleObjectOnSide(parentObject)) { report("NOT sole object on side, so selecting the parent object\n"); realObject = parentObject; } else { report("Is SOLE object on side, so selecting just the object, not its parent\n"); } } else { report("NOT a special multiply group : Integer - Implicit Multiply - Variable\n"); } } // 45+2B-4+8Z=B reportTabOut(); return realObject; } Algebra.handleClickOnObject = function(object) { // This is called by both the onMouseUP when it detects a click // and onMouseMove when it detects a drag (because he may be // trying to drag a currently non-selected object). //report("IN Algebra.handleClickOnObject(object)\n"); adviseUserClear(); if (!object.isClickSelectable || (object.isClickSelectable == false)) { // This object is not click selectable. // E.g. GroupSequenceAddSubtract is not selectable so that the user cannot inadvertantly click on some // exposed area of the GroupSequenceAddSubtract object, and not explicitly on one of its contents. // If we allowed the GroupSequenceAddSubtract object itself to be clickable we would get that GroupSequenceAddSubtract // in the g_mySelectedObjects instead of the set of individual selected objects within it. And we really do need // g_mySelectedObjects to have the individual objects rather than the GroupSequenceAddSubtract object. // Bascially, any object which itself does not have a visual representation is not click selectable. // Also, brackets and drop bars are not click selectable, although they are "drag over" selectable. return; } var newlyClickedObject = null; var newlyClickedObjectGroupParent = {}; var newlyClickedObjectGroupParentId = null; //report("TO Algebra.getRealObject(object);\n"); newlyClickedObject = Algebra.getRealObject(object); //report("BACK FROM Algebra.getRealObject(object);\n"); if (!newlyClickedObject || newlyClickedObject == null) { reportProgrammingError("Algebra.handleClickOnObject","Failed to find 'real' object. Group element type object that does not have a parent!\n"); alert("ERROR Algebra.handleClickOnObject. Failed to find 'real' object. With group element type object that does not have a parent!\n"); Algebra.clearMouseParameters(); return false; // Cancel event. } // At ths point, the newlyClickedObject should be either the original object, if it is itself one of our special objects ( paren, division line, digit or letter), // or the group parent of the original object, if the original object is not a special type object. //report("newlyClickedObject " + newlyClickedObject.className + " iD = " + newlyClickedObject.iD + " = " + newlyClickedObject.value + "\n"); if (!newlyClickedObject.setDisplayState) { // The clicked object doesn't even have a setDisplayState method, // so no need to do anything with it. //report("The clicked object doesn't even have a setDisplayState method\n"); //report("so no need to do anything with it.\n"); return false; // Cancel event. } // We appear to have acquired the selected object. var isGroupParentheses = (newlyClickedObject instanceof Algebra.GroupParentheses); var isDivision = (newlyClickedObject instanceof Algebra.GroupFraction); //report("TO get neighbors\n"); var neighborObjectLeft = Algebra.getNeighbor(newlyClickedObject.iD, k_neighborDirectionLeft); var neighborObjectRight = Algebra.getNeighbor(newlyClickedObject.iD, k_neighborDirectionRight); //report("BACK FROM get neighbors\n"); var newlyClickedObjectIsNotYetSelected = newlyClickedObject.currentDisplayState != k_displayStateSelected; if (newlyClickedObjectIsNotYetSelected) { // NOT YET SELECTED //report("newlyClickedObjectIsNotYetSelected\n"); if (Algebra.isObjectZero(newlyClickedObject)) { Algebra.soundPlayUhOh(); return; } // We now must determine whether any previously selected objects are NOT part of the same group, // and de-select them if there are. //report("So checking for other selected objects to DE-select\n"); //reportTabIn(); //report("To Algebra.getGroupParent of the just selected object = " + object.className + " = " + object.value + "\n"); newlyClickedObjectGroupParent = Algebra.getGroupParent(newlyClickedObject); //report("Back from Algebra.getGroupParent\n"); if (!newlyClickedObjectGroupParent || !newlyClickedObjectGroupParent.iD) { reportProgrammingError("Algebra.handleClickOnObject","Could not locate the group parent."); Algebra.clearMouseParameters(); return false; // Cancel event. } newlyClickedObjectGroupParentId = newlyClickedObjectGroupParent.iD; //report("newlyClickedObjectGroupParentId = " + newlyClickedObjectGroupParentId + "\n"); //report("g_algebraPreviouslySelectedObjectGroupParentId = " + g_algebraPreviouslySelectedObjectGroupParentId + "\n"); if (g_algebraPreviouslySelectedObjectGroupParentId && (g_algebraPreviouslySelectedObjectGroupParentId.length != 0)) { // SELECTING WHEN THERE IS ALREADY SOME SELECTED ITEM(S) // There is indeed an already selected object. //report("There is indeed an already selected object\n"); //report("SELECTING WHEN THERE IS ALREADY SOME SELECTED ITEM(S)\n"); if (newlyClickedObjectGroupParentId != g_algebraPreviouslySelectedObjectGroupParentId) { // Then there is one or more other selected objects which are NOT part of // the group of objects that the user just selected. // So we need to de-select those other objects. //report("Newly selected and previously selected have DIFFERENT parents.\n"); //report("NEWLY SELECT groupParentId = " + newlyClickedObjectGroupParentId + "\nPREVIOUSLY SELECTED g_algebraPreviouslySelectedObjectGroupParentId = " + g_algebraPreviouslySelectedObjectGroupParentId + "\n"); var previouslySelectedGroupParentObject = Algebra.getObjectById(g_myAlgebraObject.equation, g_algebraPreviouslySelectedObjectGroupParentId); if (previouslySelectedGroupParentObject == null) { reportNullObject("Algebra.handleClickOnObject","previouslySelectedGroupParentObject"); reportProgrammingError("Algebra.handleClickOnObject","Thought there was a previously selected object that should be de-selected but could not find it."); Algebra.clearMouseParameters(); return false; // Cancel event. } //report("DE-selecting other objects which do not share the same parent.\n"); previouslySelectedGroupParentObject.setDisplayState(k_displayStateNormal); // And empty our Selected Objects array //report("And empty our Selected Objects array.\n"); g_mySelectedObjects.empty(); } else { // Newly selected and previously selected objects are from the same group, // so no need to de-select anything. //report("Newly selected and previously selected have the SAME parent.\n"); } } else { //report("NO previously selected object\n"); // No previously selected object, so no need to de-select anything. // But empty out or drag path. g_dragPath.length = 0; // And add the positionof this selected object as the first poisition (which will // end up being the LAST position when (if) we snap the object back to its original position. g_dragPath.push(getHTMLObjectPositionRelativeToContainer("EquationManipulationBox", newlyClickedObject.iD)); } // Deal with the issue of whether there is an operator to its left (if it's a non-operator) or // a non-operator object to its right (if it's an operator). if (Algebra.isNonOperator(newlyClickedObject)) { // NON-OPERATOR : May have an operator to its left. //report("newlyClickedObject is NON-OPERATOR\n"); if (neighborObjectLeft && !Algebra.isNonOperator(neighborObjectLeft)) { //report("IS OPERATOR object to LEFT\n"); // There is an object to its left. // And that object to its left is indeed an operator. // Add the operator object to its left to our array of selected items. // Add a property to the object indicating the document-relative offset of its current position, // which is its "absolute position". // This will be needed if we subsequently move it back to where it came from. neighborObjectLeft.setDisplayState(k_displayStateSelected); neighborObjectLeft.positionRelativeToContainer = getHTMLObjectPositionRelativeToContainer("EquationManipulationBox", neighborObjectLeft.iD); g_mySelectedObjects.add(neighborObjectLeft.iD, neighborObjectLeft); } else { //report("NOT OPERATOR object to LEFT\n"); } // Now also add the newly selected object. //report("IN THINK I AM : newlyClickedObject.setDisplayState(k_displayStateSelected);\n"); newlyClickedObject.setDisplayState(k_displayStateSelected); //report("BACK FROM: newlyClickedObject.setDisplayState(k_displayStateSelected);\n"); // And then add this item to our array of selected items. // Add a property to the object indicating the document-relative offset of its current position, // which is its "absolute position". // This will be needed if we subsequently move it back to where it came from. newlyClickedObject.positionRelativeToContainer = getHTMLObjectPositionRelativeToContainer("EquationManipulationBox", newlyClickedObject.iD); g_mySelectedObjects.add(newlyClickedObject.iD, newlyClickedObject); } else { // SELECTED OBJECT IS OPERATOR //report("newlyClickedObject is OPERATOR\n"); // It's an operator type object, so there may be a non-operator object to its right. // Start by adding this item to our array of selected items. // Add a property to the object indicating the document-relative offset of its current position, // which is its "absolute position". // This will be needed if we subsequently move it back to where it came from. newlyClickedObject.setDisplayState(k_displayStateSelected); newlyClickedObject.positionRelativeToContainer = getHTMLObjectPositionRelativeToContainer("EquationManipulationBox", newlyClickedObject.iD); g_mySelectedObjects.add(newlyClickedObject.iD, newlyClickedObject); // NON-OPERATOR NEIGHBOR OBJECT TO RIGHT? // Then determine whether there is a non-operator to its right and add that to our selected Objects array if there is. if (neighborObjectRight && Algebra.isNonOperator(neighborObjectRight)) { //report("IS NON-OPERATOR object to RIGHT\n"); // Yes, we have a non-operator to its right. So add it. neighborObjectRight.setDisplayState(k_displayStateSelected); neighborObjectRight.positionRelativeToContainer = getHTMLObjectPositionRelativeToContainer("EquationManipulationBox", neighborObjectRight.iD); g_mySelectedObjects.add(neighborObjectRight.iD, neighborObjectRight); } else { //report("NOT A NON-OPERATOR object to RIGHT\n"); } } // Remember that this was the most recently selected object (i.e. remember its parent ID). g_algebraPreviouslySelectedObjectGroupParentId = newlyClickedObjectGroupParentId; //report("g_algebraPreviouslySelectedObjectGroupParentId now = "+g_algebraPreviouslySelectedObjectGroupParentId+"\n"); } else { // ALREADY SELECTED : MUST BE A DE-SELECT //report("newlyClickedObjectIs ALREADY Selected, must de-select.\n"); // So the user is DE-selecting this object. // NOTE WARNING: What about when the user explicitly de-selects ALL previously selected objects? // Should I also clear the g_algebraPreviouslySelectedObjectGroupParentId? Or doesn't it matter? // IT COULD matter if that object no longer exists!! // Find out what the parent group object is and gather up the currently selected objects within that group. //report("1 TO var groupParentObject = Algebra.getGroupParent(newlyClickedObject);\n"); var groupParentObject = Algebra.getGroupParent(newlyClickedObject); //report("1 BACK FROM var groupParentObject = Algebra.getGroupParent(newlyClickedObject);\n"); // There absolutely SHOULD be a group parent, because there is a selected object, which MUST be part of a group object. if (!groupParentObject || (groupParentObject == null) || !groupParentObject.objects || (groupParentObject.objects == null) || (groupParentObject.objects.length == 0)) { reportNullObject("Algebra.handleClickOnObject","groupParentObject"); return; } var selectedObjectsWithinParentGroup = new Array(); //report("g_algebraPreviouslySelectedObjectGroupParentId = "+g_algebraPreviouslySelectedObjectGroupParentId+"\n"); var numberOfObjects = groupParentObject.objects.length; //report("numberOfObjects in parent group = "+numberOfObjects+"\n"); //report("Looking for the group's contained objects: START\n"); //reportTabIn(); for (var index = 0 ; index < numberOfObjects ; ++index) { var object = groupParentObject.objects[index]; if (object && (object != null) && (object.currentDisplayState) && (object.currentDisplayState == k_displayStateSelected)) { //report("Saving object "+object.iD+" in array.\n"); selectedObjectsWithinParentGroup.push(object); } else { //report("Bypassing "+object.className+"\n"); } } //reportTabOut(); //report("Looking for the group's contained objects: END\n"); // Now de-select EVERYTHNG ! (We'll add back the specific selected items within this group, after that.) g_myAlgebraObject.equation.setDisplayState(k_displayStateNormal); // And re-select those objects that were identified above as being part of the group that is associated with the // parent object of the one we will, soon, de-select. g_mySelectedObjects.empty(); numberOfObjects = selectedObjectsWithinParentGroup.length; //report("Re-select "+numberOfObjects+" objects within the group and re-add to g_mySelectedObjects\n"); for (var index = 0 ; index < numberOfObjects ; ++index) { var object = selectedObjectsWithinParentGroup[index]; if (object && (object != null)) { if (object.setDisplayState) { //report("Re-selecting object = "+object.className+" : "+object.iD+"\n"); object.setDisplayState(k_displayStateSelected); // Needed in case we need to snap it back to where it came from. object.positionRelativeToContainer = getHTMLObjectPositionRelativeToContainer("EquationManipulationBox", object.iD); g_mySelectedObjects.add(object.iD, object); } else { reportProgrammingError("Algebra.handleClickOnObject",object.className+" does not have a setDisplayState method."); } } } //report("After re-selecting those within the group\n"); //g_mySelectedObjects.report(); // NOW we are finaly ready to actually de-select the clicked object. //report("About to delesect the clicked-on object\n"); if (Algebra.isNonOperator(newlyClickedObject)) { // NON-OPERATOR : May have an operator to its left. if (neighborObjectLeft && !Algebra.isNonOperator(neighborObjectLeft)) { // There is an object to its left. // And that object to its left is indeed an operator. // Remove the operator object to its left from our array of selected items. neighborObjectLeft.setDisplayState(k_displayStateNormal); g_mySelectedObjects.remove(neighborObjectLeft.iD); } // Now also remove the newly de-selected object from our array. newlyClickedObject.setDisplayState(k_displayStateNormal); // And then remove this item from our array of selected items. g_mySelectedObjects.remove(newlyClickedObject.iD); } else { // SELECTED OBJECT IS OPERATOR // It's an operator type object, so there may be a non-operator object to its right. // Start by removing this item from our array of selected items. newlyClickedObject.setDisplayState(k_displayStateNormal); g_mySelectedObjects.remove(newlyClickedObject.iD); // NON-OPERATOR NEIGHBOR OBJECT TO RIGHT? // Then determine whether there is a non-operator to its right and remove that from our selected Objects array if there is one. if (neighborObjectRight && Algebra.isNonOperator(neighborObjectRight)) { // Yes, we have a non-operator to its right. So remove it. neighborObjectRight.setDisplayState(k_displayStateNormal); g_mySelectedObjects.remove(neighborObjectRight.iD); } } } //report("g_algebraPreviouslySelectedObjectGroupParentId ID NOW = " + g_algebraPreviouslySelectedObjectGroupParentId + "\n"); //reportTabOut(); //report("DONE checking for other selected objects to DE-select\n"); reportTruth("areSelectedObjectsContiguous",Algebra.areSelectedObjectsContiguous()); reportTruth("areSelectedObjectsLikeObjects",Algebra.areSelectedObjectsLikeObjects()); reportTruth("areSelectedObjectsNumbers",g_SelectedObjectsType == k_SelectedObjectsType_SimpleNumerics); reportTruth("areSelectedObjectsVariables",g_SelectedObjectsType == k_SelectedObjectsType_VariablesOrSpecialMultiplies); reportTruth("areSelectedObjectsFractions",g_SelectedObjectsType == k_SelectedObjectsType_Fractions); //g_mySelectedObjects.report(); } // This allows for nestingof show/unshow calls. var g_hourglassCursorShowCount = 0; function hourglassCursorShow() { if (g_hourglassCursorShowCount == 0) { document.body.style.cursor = "wait"; //alert("hourglassCursorShow"); } ++g_hourglassCursorShowCount; } function hourglassCursorUnShow() { //adviseUser("hourglassCursor UN Show", k_adviseUserImportance_Normal); --g_hourglassCursorShowCount; if (g_hourglassCursorShowCount < 0) { g_hourglassCursorShowCount = 0; } if (g_hourglassCursorShowCount == 0) { document.body.style.cursor = "default"; } //adviseUser("hourglassCursor UN Show "+g_hourglassCursorShowCount, k_adviseUserImportance_Normal); } /////////////////////////////////////////////////////// // // BUTTON CLICK // /////////////////////////////////////////////////////// function onClickGo() { if (!isCompletelyLoaded()) return; //report("IN onClickGo()\n"); reportClear(); adviseUserClear(); reportTruth("areSelectedObjectsContiguous",false); g_mySelectedObjects.empty(); var field = document.getElementById("OriginalEquation"); equationText = field.value; g_myAlgebraObject.lexicalAnalyze(equationText, 'EquationManipulationBox'); // } function onClickNext() { if (!isCompletelyLoaded()) return; adviseUserClear(); report("IN onClickNext()\n"); reportTabIn(); if (ComboBox.selectItemNext("ComboBoxEquationSelector")) { var selectedEquation = ComboBox.getSelectedText("ComboBoxEquationSelector"); var field = document.getElementById("OriginalEquation"); field.value = selectedEquation; onClickGo(); } reportTabOut(); } function onClickPrev() { if (!isCompletelyLoaded()) return; adviseUserClear(); //report("IN onClickPrev()\n"); if (ComboBox.selectItemPrevious("ComboBoxEquationSelector")) { var selectedEquation = ComboBox.getSelectedText("ComboBoxEquationSelector"); var field = document.getElementById("OriginalEquation"); field.value = selectedEquation; onClickGo(); } } function onClickShowGroupBrackets() { if (!isCompletelyLoaded()) return; // This is a checkbox. var checkboxShowGroupBrackets = document.getElementById("ShowGroupBrackets"); g_isEnabledGroupingBrackets = checkboxShowGroupBrackets.checked; if (g_hasDoneASuccessfulLexicalAnalyze) { g_myAlgebraObject.putObjectsInEquationManipulationBox(); } } var g_showImplicitMultiplicationSigns = false; function onClickShowImplicitMultiplicationSigns() { if (!isCompletelyLoaded()) return; // This is a checkbox. var checkboxShowImplicitMultiplicationSigns = document.getElementById("ShowImplicitMultiplicationSigns"); g_showImplicitMultiplicationSigns = checkboxShowImplicitMultiplicationSigns.checked; if (g_hasDoneASuccessfulLexicalAnalyze) { g_myAlgebraObject.putObjectsInEquationManipulationBox(); } } function onClickEnableSounds() { if (!isCompletelyLoaded()) return; // This is a checkbox. var checkboxEnableSounds = document.getElementById("EnableSounds"); g_soundsAreEnabled = checkboxEnableSounds.checked; } var g_showDropBars = false; function onClickShowDropBars() { if (!isCompletelyLoaded()) return; // This is a checkbox. var checkboxShowDropBars = document.getElementById("ShowDropBars"); g_showDropBars = checkboxShowDropBars.checked; if (g_hasDoneASuccessfulLexicalAnalyze) { g_myAlgebraObject.putObjectsInEquationManipulationBox(); } } function onClickClearDiagnostics() { if (!isCompletelyLoaded()) return; adviseUserClear(); reportClear(); } function onClickFOIL() { if (!isCompletelyLoaded()) return; adviseUserClear(); adviseUser("That button is not yet implemented.", k_adviseUserImportance_Normal); Algebra.soundPlayUhOh(); } function onClickDistributiveProperty() { if (!isCompletelyLoaded()) return; adviseUserClear(); adviseUser("That button is not yet implemented.", k_adviseUserImportance_Normal); Algebra.soundPlayUhOh(); } function onClickCombineLikeTerms() { if (!isCompletelyLoaded()) return; report("IN onClickCombineLikeTerms\n"); reportTabIn(); adviseUserClear(); var numberOfSelectedAlgebraObjects = g_mySelectedObjects.getNumberOfAlgebraObjects(); if (numberOfSelectedAlgebraObjects <= 0) { adviseUser("No objects are selected. Select objects to combine before using this button", k_adviseUserImportance_Normal); Algebra.soundPlayUhOh(); reportTabOut(); return; } // The objects must be part of an add/subtract group!!! var oneOfTheSelectedObjects = g_mySelectedObjects.getObject(0); //var parent = Algebra.getObjectById(g_myAlgebraObject.equation, oneOfTheSelectedObjects.parentObjectId); var parent = oneOfTheSelectedObjects.parentObject; if (!(parent instanceof Algebra.GroupSequenceAddSubtract)) { adviseUser("Only add/subtract objects may be combined.", k_adviseUserImportance_Normal); Algebra.soundPlayUhOh(); reportTabOut(); return; } if (numberOfSelectedAlgebraObjects < 2) { adviseUser("Only one object is selected. Two or more objects must be selected before using this button", k_adviseUserImportance_Normal); Algebra.soundPlayUhOh(); reportTabOut(); return; } if (!Algebra.areSelectedObjectsContiguous()) { adviseUser("The selected objects must be next to one another before attempting to combine them.", k_adviseUserImportance_Normal); Algebra.soundPlayUhOh(); reportTabOut(); return; } if (!Algebra.areSelectedObjectsLikeObjects()) { adviseUser("The selected objects are not 'LIKE' objects. They cannot be combined.", k_adviseUserImportance_Normal); Algebra.soundPlayUhOh(); reportTabOut(); return; } // OK, we seem to have contiguous, like, objects selected and can therefore combine them. // We should also have the g_SelectedObjectsType variable, which should have gotten set by the // call to Algebra.areSelectedObjectsLikeObjects(); report("oneOfTheSelectedObjects = "+oneOfTheSelectedObjects.iD+"\n"); //var parent = Algebra.getObjectById(g_myAlgebraObject.equation, oneOfTheSelectedObjects.parentObjectId); var parent = oneOfTheSelectedObjects.parentObject; if (!parent || (parent == null)) { reportNullObject("onClickCombineLikeTerms()","parent"); reportTabOut(); return; } report("parent = "+parent.iD+"\n"); var operatorIsAdd = true; // false means operator is minus. var numerator = parseInt(0); var objects = parent.objects; var numberOfObjects = objects.length; report("numberOfObjects = "+numberOfObjects+"\n"); Algebra.showObjectArray("parent.objects", parent.objects); report("Determine the number of non-selected, non-dropbar objects.\n"); // Determine the number of non-selected, non-dropbar objects. // This is used later on when we substitute the new value into the existing set of // objects. In the case where the total ends up being ZERO we need to know // whether or