package org.geogebra.common.kernel; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import org.geogebra.common.euclidian.EuclidianConstants; import org.geogebra.common.euclidian.event.PointerEventType; import org.geogebra.common.io.MyXMLio; import org.geogebra.common.kernel.algos.AlgoCasBase; import org.geogebra.common.kernel.algos.AlgoDependentNumber; import org.geogebra.common.kernel.algos.AlgoDistancePoints; import org.geogebra.common.kernel.algos.AlgoElement; import org.geogebra.common.kernel.algos.AlgoJoinPointsSegment; import org.geogebra.common.kernel.algos.AlgorithmSet; import org.geogebra.common.kernel.algos.ConstructionElement; import org.geogebra.common.kernel.arithmetic.Equation; import org.geogebra.common.kernel.arithmetic.ExpressionNode; import org.geogebra.common.kernel.arithmetic.ExpressionNodeConstants; import org.geogebra.common.kernel.arithmetic.MyArbitraryConstant; import org.geogebra.common.kernel.arithmetic.NumberValue; import org.geogebra.common.kernel.arithmetic.ValidExpression; import org.geogebra.common.kernel.cas.AlgoDependentCasCell; import org.geogebra.common.kernel.cas.AlgoUsingTempCASalgo; import org.geogebra.common.kernel.cas.UsesCAS; import org.geogebra.common.kernel.geos.GeoAxis; import org.geogebra.common.kernel.geos.GeoBoolean; import org.geogebra.common.kernel.geos.GeoCasCell; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoElementSpreadsheet; import org.geogebra.common.kernel.geos.GeoNumberValue; import org.geogebra.common.kernel.geos.GeoNumeric; import org.geogebra.common.kernel.geos.GeoPoint; import org.geogebra.common.kernel.geos.GeoSegment; import org.geogebra.common.kernel.geos.GeoVector; import org.geogebra.common.kernel.kernelND.GeoAxisND; import org.geogebra.common.kernel.kernelND.GeoDirectionND; import org.geogebra.common.kernel.kernelND.GeoElementND; import org.geogebra.common.kernel.kernelND.GeoPointND; import org.geogebra.common.kernel.optimization.ExtremumFinderI; import org.geogebra.common.kernel.prover.AlgoLocusEquation; import org.geogebra.common.kernel.prover.AlgoProve; import org.geogebra.common.kernel.prover.AlgoProveDetails; import org.geogebra.common.main.App; import org.geogebra.common.main.Localization; import org.geogebra.common.main.MyError; import org.geogebra.common.main.SelectionManager; import org.geogebra.common.plugin.GeoClass; import org.geogebra.common.plugin.Operation; import org.geogebra.common.util.StringUtil; import org.geogebra.common.util.debug.Log; /** * Manages construction elements * * @author Markus * */ public class Construction { /** * Creates a new Construction. * * @param k * Kernel */ public Construction(Kernel k) { this(k, null); } private ConstructionCompanion companion; private long ceIDcounter = 1; private int nextVariableID = 1; /** * Creates a new Construction. * * @param k * Kernel * @param parentConstruction * parent construction (used for macro constructions) */ protected Construction(Kernel k, Construction parentConstruction) { kernel = k; companion = kernel.createConstructionCompanion(this); ceList = new ArrayList<ConstructionElement>(); algoList = new ArrayList<AlgoElement>(); step = -1; geoSetConsOrder = new TreeSet<GeoElement>(); geoSetWithCasCells = new TreeSet<GeoElement>(); geoSetLabelOrder = new TreeSet<GeoElement>(new LabelComparator()); geoSetsTypeMap = new HashMap<GeoClass, TreeSet<GeoElement>>(); euclidianViewCE = new ArrayList<EuclidianViewCE>(); if (parentConstruction != null) { consDefaults = parentConstruction.getConstructionDefaults(); } else { newConstructionDefaults(); } // consDefaults = new ConstructionDefaults(this); setIgnoringNewTypes(true); initAxis(); setIgnoringNewTypes(false); geoTable = new HashMap<String, GeoElement>(200); initGeoTables(); } /** maps arbconst indices to related numbers */ public Map<Integer, GeoNumeric> constsM = new TreeMap<Integer, GeoNumeric>(); /** maps arbint indices to related numbers */ public Map<Integer, GeoNumeric> intsM = new TreeMap<Integer, GeoNumeric>(); /** maps arbcomplex indices to related numbers */ public Map<Integer, GeoNumeric> complexNumbersM = new TreeMap<Integer, GeoNumeric>(); /** * used to keep track if file is 3D or just 2D * * cleared in Construction.newConstructionDefaults() (after default geos are * loaded) * */ private TreeSet<GeoClass> usedGeos = new TreeSet<GeoClass>(); /** * creates the ConstructionDefaults consDefaults */ final private void newConstructionDefaults() { consDefaults = companion.newConstructionDefaults(); } // list of Macro commands used in this construction // TODO: specify type once Macro is ported private ArrayList<Macro> usedMacros; /** UndoManager */ protected UndoManager undoManager; /** default elements */ protected ConstructionDefaults consDefaults; // TODO: make private once we port ClearConstruction private String title, author, date; // text for dynamic worksheets: 0 .. above, 1 .. below private String[] worksheetText = new String[2]; // showOnlyBreakpoints in construction protocol private boolean showOnlyBreakpoints; /** construction belongs to kernel */ protected Kernel kernel; // current construction step (-1 ... ceList.size() - 1) // step == -1 shows empty construction private int step; // in macro mode no new labels or construction elements // can be added private boolean supressLabelCreation = false; // a map for sets with all labeled GeoElements in alphabetical order of // specific types // (points, lines, etc.) // private HashMap<GeoClass, TreeSet<GeoElement>> geoSetsTypeMap; // ConstructionElement List (for objects of type ConstructionElement) private ArrayList<ConstructionElement> ceList; // AlgoElement List (for objects of type AlgoElement) private ArrayList<AlgoElement> algoList; // used in updateConstruction() /** Table for (label, GeoElement) pairs, contains global variables */ protected HashMap<String, GeoElement> geoTable; // list of algorithms that need to be updated when EuclidianView changes private ArrayList<EuclidianViewCE> euclidianViewCE; private ArrayList<EuclidianViewCE> corner5Algos; private ArrayList<EuclidianViewCE> corner11Algos; /** Table for (label, GeoElement) pairs, contains local variables */ protected HashMap<String, GeoElement> localVariableTable; // set with all labeled GeoElements in ceList order private TreeSet<GeoElement> geoSetConsOrder; // set with all labeled GeoElements in alphabetical order private TreeSet<GeoElement> geoSetLabelOrder; private TreeSet<GeoElement> geoSetWithCasCells; // table of arbitraryConstants with casTable row key private HashMap<Integer, MyArbitraryConstant> arbitraryConsTable = new HashMap<Integer, MyArbitraryConstant>(); // list of random numbers or lists private TreeSet<GeoElement> randomElements; /** * Table for (label, GeoCasCell) pairs, contains global variables used in * CAS view */ protected HashMap<String, GeoCasCell> geoCasCellTable; // collect replace() requests to improve performance // when many cells in the spreadsheet are redefined at once private boolean collectRedefineCalls = false; private HashMap<GeoElement, GeoElement> redefineMap; private GeoElement keepGeo; private ArrayList<GeoElement> latexGeos; /** * @return geo temporarily kept inside this construction */ public GeoElement getKeepGeo() { return keepGeo; } // axis objects private GeoAxis xAxis, yAxis; private String xAxisLocalName, yAxisLocalName; private GeoPoint origin; private GeoElement selfGeo; /** * Returns the point (0,0) * * @return point (0,0) */ public final GeoPoint getOrigin() { if (origin == null) { origin = new GeoPoint(this); origin.setCoords(0.0, 0.0, 1.0); } return origin; } /** * @param selfGeo * new value of "self" variable */ public void setSelfGeo(GeoElement selfGeo) { this.selfGeo = selfGeo; } /** * Returns x-axis * * @return x-axis */ final public GeoAxis getXAxis() { return xAxis; } /** * Returns y-axis * * @return y-axis */ final public GeoAxis getYAxis() { return yAxis; } /** * init the axis */ final private void initAxis() { xAxis = new GeoAxis(this, GeoAxisND.X_AXIS); yAxis = new GeoAxis(this, GeoAxisND.Y_AXIS); companion.init(); } /** * Construction constants (xAxis, yAxis, ...) * */ public enum Constants { /** * not a constant */ NOT, /** * x axis */ X_AXIS, /** * y axis */ Y_AXIS, /** * z axis */ Z_AXIS, /** * xOy plane */ XOY_PLANE, /** * space */ SPACE } /** * * @param geo * geo * @return which constant geo (xAxis, yAxis, ...) */ final public Constants isConstantElement(GeoElement geo) { if (geo == xAxis) { return Constants.X_AXIS; } if (geo == yAxis) { return Constants.Y_AXIS; } return companion.isConstantElement(geo); } /** * Renames xAxis and yAxis in the geoTable and sets *AxisLocalName-s * acordingly */ final public void updateLocalAxesNames() { geoTable.remove(xAxisLocalName); geoTable.remove(yAxisLocalName); Localization app = kernel.getLocalization(); xAxisLocalName = app.getMenu("xAxis"); yAxisLocalName = app.getMenu("yAxis"); geoTable.put(xAxisLocalName, xAxis); geoTable.put(yAxisLocalName, yAxis); companion.updateLocalAxesNames(); } /** * Returns the construction default object of this construction. * * @return construction default object of this construction. */ final public ConstructionDefaults getConstructionDefaults() { return consDefaults; } /** * @return table of arbitraryConstants from CAS with assigmentVar key */ public HashMap<Integer, MyArbitraryConstant> getArbitraryConsTable() { return arbitraryConsTable; } /** * @param arbitraryConsTable * - table of arbitraryConstants from CAS with assigmentVar key */ public void setArbitraryConsTable( HashMap<Integer, MyArbitraryConstant> arbitraryConsTable) { this.arbitraryConsTable = arbitraryConsTable; } /** * Returns construction's author * * @return construction's author */ public String getAuthor() { return (author != null) ? author : ""; } /** * Returns construction's date * * @return construction's date */ public String getDate() { return (date != null) ? date : ""; } /** * Returns construction's title * * @return construction's title */ public String getTitle() { return (title != null) ? title : ""; } /** * Sets construction's author * * @param string * new author */ public void setAuthor(String string) { author = string; } /** * Sets construction's date * * @param string * new date */ public void setDate(String string) { date = string; } /** * Sets construction's title * * @param string * new title */ public void setTitle(String string) { title = string; } /** * Returns part of worksheet text * * @param i * 0 for first part, 1 for second part * @return given part of worksheet text */ public String getWorksheetText(int i) { return (worksheetText[i] != null) ? worksheetText[i] : ""; } /** * Sets part of worksheet text * * @param i * 0 for first part, 1 for second part * @param text * new text for that part */ public void setWorksheetText(String text, int i) { worksheetText[i] = text; } /** * TODO: make private again * * @return true if at least one text is nonempty */ protected boolean worksheetTextDefined() { for (int i = 0; i < worksheetText.length; i++) { if (worksheetText[i] != null && worksheetText[i].length() > 0) { return true; } } return false; } /** * Returns current kernel * * @return current kernel */ public Kernel getKernel() { return kernel; } /** * If this is set to true new construction elements won't get labels. * * @param flag * true iff labelcreation should be supressed */ public void setSuppressLabelCreation(boolean flag) { supressLabelCreation = flag; } /** * Returns true iff new construction elements won't get labels. * * @return true iff new construction elements won't get labels. */ public boolean isSuppressLabelsActive() { return supressLabelCreation; } /** * Returns current application * * @return current application */ public App getApplication() { return kernel.getApplication(); } /** * Tests if this construction has no elements. * * @return true if this construction has no GeoElements; false otherwise. */ public boolean isEmpty() { return ceList.isEmpty(); } /** * Returns the total number of construction steps. * * @return Total number of construction steps. */ public int steps() { return ceList.size(); } /** * Returns the last GeoElement object in the construction list. * * @return the last GeoElement object in the construction list. */ public GeoElement getLastGeoElement() { if (geoSetWithCasCells.size() > 0) { return geoSetWithCasCells.last(); } return null; } /*** * Returns the n-th GeoCasCell object (free or dependent) in the * construction list. This is the GeoCasCell in the n-th row of the CAS * view. * * @param row * number starting at 0 * @return cas cell or null if there are less cas cells in the construction * list */ public GeoCasCell getCasCell(int row) { if (row < 0) { return null; } int counter = 0; for (ConstructionElement ce : ceList) { if (ce instanceof GeoCasCell) { if (counter == row) { return (GeoCasCell) ce; } ++counter; } else if (ce instanceof AlgoCasCellInterface) { if (counter == row) { return ((AlgoCasCellInterface) ce).getCasCell(); } ++counter; } } // less than n casCell return null; } /*** * Returns the last GeoCasCell object (free or dependent) in the * construction list. * * @return last cas cell */ public GeoCasCell getLastCasCell() { GeoCasCell lastCell = null; for (ConstructionElement ce : ceList) { if (ce instanceof GeoCasCell) { lastCell = (GeoCasCell) ce; } else if (ce instanceof AlgoCasCellInterface) { lastCell = ((AlgoCasCellInterface) ce).getCasCell(); } } return lastCell; } /*** * Adds the given GeoCasCell object to the construction list so that it * becomes the n-th GeoCasCell in the list. Other cas cells are shifted * right. * * @param casCell * CAS cell to be added to construction list * * @param n * number starting at 0 */ public void setCasCellRow(GeoCasCell casCell, int n) { GeoCasCell nthCasCell = getCasCell(n); if (nthCasCell == null) { addToConstructionList(casCell, false); } else { addToConstructionList(casCell, nthCasCell.getConstructionIndex()); } addToGeoSetWithCasCells(casCell); } /** * Adds a geo to the list of local variables using the specified local * variable name . * * @param varname * local variable name * @param geo * local variable object */ final public void addLocalVariable(String varname, GeoElement geo) { if (localVariableTable == null) { localVariableTable = new HashMap<String, GeoElement>(); } localVariableTable.put(varname, geo); geo.setLocalVariableLabel(varname); } /** * Removes local variable of given name. Note that the underlying GeoElement * object gets back its previous label as a side effect. * * @param varname * name of variable to be removed */ final public void removeLocalVariable(String varname) { if (localVariableTable != null) { GeoElement geo = localVariableTable.remove(varname); if (geo != null) { geo.undoLocalVariableLabel(); } } } /** * Looks for geo with given label, doesn't work for e.g. A$1 * * @param label * Label to be looked up * @return Geo with given label */ public GeoElement geoTableVarLookup(String label) { GeoElement ret = geoTable.get(label); return ret; } /** * Looks for equation with given label * * @param label * - label of the searched geo * @return returns the equation defined by label in CAS */ public ValidExpression geoCeListLookup(String label) { for (int i = 0; i < ceList.size(); i++) { if (ceList.get(i) instanceof GeoCasCell) { // get current cell GeoCasCell currCell = (GeoCasCell) ceList.get(i); // we found the equation if (currCell.getInput(StringTemplate.defaultTemplate) .startsWith(label + "=") && ((ExpressionNode) currCell.getInputVE()) .getLeft() instanceof Equation) { // return the equation return (ValidExpression) ((ExpressionNode) currCell .getInputVE()).getLeft(); } } } return null; } /** * Sets how steps in the construction protocol are handled. * * @param flag * true iff construction protocol should show only breakpoints */ public void setShowOnlyBreakpoints(boolean flag) { showOnlyBreakpoints = flag; } /** * True iff construction protocol should show only breakpoints * * @return true iff construction protocol should show only breakpoints */ final public boolean showOnlyBreakpoints() { return showOnlyBreakpoints; } /** * TODO:Private * * @param pos * position */ protected void updateConstructionIndex(int pos) { if (pos < 0) { return; } int size = ceList.size(); for (int i = pos; i < size; ++i) { ceList.get(i).setConstructionIndex(i); } } /** * Updates all algos * * @author Michael Borcherds * @version 2008-05-15 * @return true iff there were any algos that wanted update TODO make * private again */ protected final boolean updateAllConstructionProtocolAlgorithms() { // Application.debug("updateAllConstructionProtocolAlgorithms"); // update all algorithms int size = algoList.size(); ArrayList<AlgoElement> updateAlgos = null; for (int i = 0; i < size; ++i) { AlgoElement algo = algoList.get(i); if (algo.wantsConstructionProtocolUpdate()) { if (updateAlgos == null) { updateAlgos = new ArrayList<AlgoElement>(); } updateAlgos.add(algo); } } // propagate update down all dependent GeoElements if (updateAlgos != null) { AlgoElement.updateCascadeAlgos(updateAlgos); } if (updateAlgos != null) { App app = kernel.getApplication(); if (app.isUsingFullGui() && app.getGuiManager() != null) { app.getGuiManager().updateConstructionProtocol(); } } return updateAlgos != null; } /** * Adds the given Construction Element to this Construction at position * index * * @param ce * element to be added * @param index * index */ public void addToConstructionList(ConstructionElement ce, int index) { ++step; ceList.add(index, ce); updateConstructionIndex(index); // update cas row references if (ce instanceof GeoCasCell) { updateCasCellRows(); } updateAllConstructionProtocolAlgorithms(); } /** * Tells all GeoCasCells that the order of cas cells may have changed. They * can then update their row number and input strings with row references. */ public void updateCasCellRows() { // update all row numbers first int counter = 0; for (ConstructionElement ce : ceList) { if (ce instanceof GeoCasCell) { ((GeoCasCell) ce).setRowNumber(counter); counter++; } else if (ce instanceof AlgoCasCellInterface) { ((AlgoCasCellInterface) ce).getCasCell().setRowNumber(counter); counter++; } } // now update all row references for (ConstructionElement ce : ceList) { if (ce instanceof GeoCasCell) { ((GeoCasCell) ce).updateInputStringWithRowReferences(); } else if (ce instanceof AlgoCasCellInterface) { ((AlgoCasCellInterface) ce).getCasCell() .updateInputStringWithRowReferences(); } } } /** * Moves object at position from to position to in this construction. * * @param fromIndex * index of element to be moved * @param toIndex * target index of this element * @return whether construction list was changed or not. */ public boolean moveInConstructionList(int fromIndex, int toIndex) { // check if move is possible ConstructionElement ce = ceList.get(fromIndex); boolean change = fromIndex != toIndex && ce.getMinConstructionIndex() <= toIndex && toIndex <= ce.getMaxConstructionIndex(); if (change) { if (ce instanceof GeoElement) { // TODO: update Algebra View Log.debug("TODO: update Algebra View"); } // move the construction element ceList.remove(fromIndex); ceList.add(toIndex, ce); // update construction indices updateConstructionIndex(Math.min(toIndex, fromIndex)); // update construction step if (fromIndex <= step && step < toIndex) { --step; ce.notifyRemove(); } else if (toIndex <= step && step < fromIndex) { ++step; ce.notifyAdd(); } // update cas row references if (ce instanceof GeoCasCell || ce instanceof AlgoCasCellInterface) { updateCasCellRows(); } updateAllConstructionProtocolAlgorithms(); // Michael Borcherds } // 2008-05-15 return change; } /** * Adds the given Construction Element to this Construction at position * getStep() + 1. * * @param ce * Construction element to be added * @param checkContains * : true to first check if ce is already in list */ public void addToConstructionList(ConstructionElement ce, boolean checkContains) { if (supressLabelCreation) { return; } if (checkContains && ce.isInConstructionList()) { return; } /* * ++step; updateAllConstructionProtocolAlgorithms(); // Michael * Borcherds // 2008-05-15 * * ceList.add(step, ce); updateConstructionIndex(step); */ addToConstructionList(ce, step + 1); } /** * Removes the given Construction Element from this Construction and updates * step if necessary (i.e. if ce.getConstructionIndex() <= getStep()). * * @param ce * ConstuctionElement to be removed */ public void removeFromConstructionList(ConstructionElement ce) { int pos = ceList.indexOf(ce); if (pos == -1) { return; } else if (pos <= step) { ceList.remove(ce); ce.setConstructionIndex(-1); --step; } else { // pos > step ceList.remove(ce); ce.setConstructionIndex(-1); } updateConstructionIndex(pos); // update cas row references if (ce instanceof GeoCasCell || (ce instanceof AlgoCasCellInterface)) { // needed for GGB-808 // remove geoCasCell from CasView table before update of cell rows for (View view : kernel.views) { if (view.getViewID() == App.VIEW_CAS) { if (ce instanceof GeoCasCell) { view.remove((GeoCasCell) ce); } if (ce instanceof AlgoDependentCasCell) { view.remove(((AlgoDependentCasCell) ce).getCasCell()); } } } updateCasCellRows(); } updateAllConstructionProtocolAlgorithms(); // Michael Borcherds // 2008-05-15 /* * if (ce.getClassName().equals("AlgoPrism")||ce.getClassName().equals( * "AlgoPyramid")) Application.printStacktrace(ce.getClassName()); else * Application.debug(ce.getClassName()); */ } /** * Adds the given algorithm to this construction's algorithm list * * @param algo * to be added * @see #updateConstruction() */ public void addToAlgorithmList(AlgoElement algo) { algoList.add(algo); } /** * The list of algo elements. * * @return list of algos */ public ArrayList<AlgoElement> getAlgoList() { return algoList; } /** * Removes the given algorithm from this construction's algorithm list * * @param algo * algo to be removed */ public void removeFromAlgorithmList(AlgoElement algo) { algoList.remove(algo); } /** * Moves geo to given position toIndex in this construction. Note: if ce (or * its parent algorithm) is not in the construction list nothing is done. * * @param geo * element to bemoved * @param toIndex * new index * * @return whether construction list was changed or not. */ public boolean moveInConstructionList(GeoElement geo, int toIndex) { AlgoElement algoParent = geo.getParentAlgorithm(); int fromIndex = (algoParent == null) ? ceList.indexOf(geo) : ceList.indexOf(algoParent); if (fromIndex >= 0) { return moveInConstructionList(fromIndex, toIndex); } return false; } /** * Returns true iff geo is independent and in the construction list or geo * is dependent and its parent algorithm is in the construction list. * * @param geo * GeoElement to be looked for * @return true iff geo or its parent algo are in construction list */ public boolean isInConstructionList(GeoElement geo) { if (geo.isIndependent()) { return geo.isInConstructionList(); } return geo.getParentAlgorithm().isInConstructionList(); } /** * Updates all algorithms in this construction */ public final void updateAllAlgorithms() { // update all algorithms // *** algoList.size() can change during the loop for (int i = 0; i < algoList.size(); ++i) { AlgoElement algo = algoList.get(i); algo.update(); // AbstractApplication.debug("#"+i+" : "+algo); } } /** * Registers an algorithm that wants to be notified when * setEuclidianViewBounds() is called. * * @param elem * construction element to be registered */ public final void registerEuclidianViewCE(EuclidianViewCE elem) { if (!euclidianViewCE.contains(elem)) { euclidianViewCE.add(elem); } } /** * Unregisters an algorithm that wants to be notified when * setEuclidianViewBounds() is called. * * @param elem * construction element to be unregistered */ public final void unregisterEuclidianViewCE(EuclidianViewCE elem) { euclidianViewCE.remove(elem); if (this.corner5Algos != null) { this.corner5Algos.remove(elem); } if (this.corner11Algos != null) { this.corner11Algos.remove(elem); } } /** * Calls euclidianViewUpdate on all registered euclidian view construction * elements Those elements which return true, will also get an update of * their dependent objects. * * @param type * changed property * * @return true iff there were any elements to update */ public boolean notifyEuclidianViewCE(EVProperty type) { boolean didUpdate = false; ArrayList<EuclidianViewCE> toUpdate = type == EVProperty.SIZE ? this.corner5Algos : (type == EVProperty.ROTATION ? this.corner11Algos : this.euclidianViewCE); if (toUpdate == null || toUpdate.size() == 0) { return false; } int size = toUpdate.size(); AlgorithmSet updateSet = null; for (int i = 0; i < size; i++) { didUpdate = true; EuclidianViewCE elem = toUpdate.get(i); boolean needsUpdateCascade = elem.euclidianViewUpdate(); if (needsUpdateCascade) { if (updateSet == null) { updateSet = new AlgorithmSet(); } if (elem instanceof GeoElement) { GeoElement geo = (GeoElement) elem; updateSet.addAllSorted(geo.getAlgoUpdateSet()); } else if (elem instanceof AlgoElement) { AlgoElement algo = (AlgoElement) elem; GeoElement[] geos = algo.getOutput(); for (GeoElement geo : geos) { updateSet.addAllSorted(geo.getAlgoUpdateSet()); } } } } if (updateSet != null) { updateSet.updateAll(); } return didUpdate; } /** * Returns true iff there are any euclidian view construction elements in * this construction * * @return true iff there are any euclidian view construction elements in * this construction */ public boolean hasEuclidianViewCE() { return euclidianViewCE.size() > 0; } /** * Updates all random numbers of this construction. */ final public void updateAllRandomGeos() { if (randomElements == null) { return; } Iterator<GeoElement> it = randomElements.iterator(); while (it.hasNext()) { GeoElement num = it.next(); num.updateRandomGeo(); } } /** * Updates all free random numbers of this construction. */ final public void updateAllFreeRandomGeosNoCascade() { if (randomElements == null) { return; } Iterator<GeoElement> it = randomElements.iterator(); while (it.hasNext()) { GeoElement num = it.next(); if (num.isGeoNumeric() && num.getParentAlgorithm() == null) { GeoNumeric number = (GeoNumeric) num; number.updateRandomNoCascade(); number.update(); } } } /** * Adds a number to the set of random numbers of this construction. * * @param num * Element to be added */ public void addRandomGeo(GeoElement num) { if (randomElements == null) { randomElements = new TreeSet<GeoElement>(); } randomElements.add(num); num.setRandomGeo(true); } /** * Removes a number from the set of random numbers of this construction. * * @param num * Element to be removed */ public void removeRandomGeo(GeoElement num) { if (randomElements != null) { randomElements.remove(num); } num.setRandomGeo(false); } /** * Update construction including random */ final public void updateConstruction() { updateConstruction(true); } /** * Updates all objects in this construction. * * @param randomize * whether to also update random algos */ final public void updateConstruction(boolean randomize) { // collect notifyUpdate calls using xAxis as dummy geo updateConstructionRunning = true; try { // G.Sturr 2010-5-28: turned this off so that random numbers can be // traced // if (!kernel.isMacroKernel() && kernel.app.hasGuiManager()) // kernel.app.getGuiManager().startCollectingSpreadsheetTraces(); // update all independent GeoElements int size = ceList.size(); for (int i = 0; i < size; ++i) { ConstructionElement ce = ceList.get(i); if (ce.isIndependent()) { ce.update(); } } // update all free random numbers() (dependent random numbers will // be updated from algo list) // no update cascade is done: algos will be updated if (randomize) { updateAllFreeRandomGeosNoCascade(); } // init and update all algorithms // make sure we call algo.initNearToRelationship() fist // for all algorithms because algo.update() could have // the side-effect to call updateCascade() for points // that have locateables (see GeoPoint.update()) size = algoList.size(); // init near to relationship for all algorithms: // this makes sure intersection points stay at their saved positions for (int i = 0; i < size; ++i) { AlgoElement algo = algoList.get(i); algo.initForNearToRelationship(); } // copy array to avoid problems with the list changing during the // loop // eg Polygon[A,B,RandomBetween[4,5]] // http://www.geogebra.org/forum/viewtopic.php?p=56618 ArrayList<AlgoElement> tempList = new ArrayList<AlgoElement>( algoList); // update all algorithms for (int i = 0; i < size; ++i) { AlgoElement algo = tempList.get(i); // reinit near to relationship to make sure points stay at their // saved position // keep this line, see // http://code.google.com/p/geogebra/issues/detail?id=62 algo.initForNearToRelationship(); // update algorithm if (algo instanceof AlgoLocusEquation) { ((AlgoLocusEquation) algo).resetFingerprint(kernel, true); } if (randomize || !(algo instanceof SetRandomValue)) { algo.update(); } } // G.Sturr 2010-5-28: // if (!kernel.isMacroKernel() && kernel.app.hasGuiManager()) // kernel.app.getGuiManager().stopCollectingSpreadsheetTraces(); } finally { updateConstructionRunning = false; } } /** * Similar to updateConstruction, but only updates CAS cells */ final public void updateCasCells() { // collect notifyUpdate calls using xAxis as dummy geo updateConstructionRunning = true; try { // update all independent GeoElements // check the size every time as Delete may change it for (int i = 0; i < ceList.size(); ++i) { ConstructionElement ce = ceList.get(i); if ((ce.isGeoElement() && ((GeoElement) ce).isGeoCasCell()) || ((ce instanceof AlgoElement) && ce instanceof AlgoCasCellInterface)) { ce.update(); } } } finally { updateConstructionRunning = false; } } /** * Returns this construction in XML format. GeoGebra File Format. * * @param sb * StringBuilder to which the XML is appended * @param getListenersToo * whether to include JS listener names */ public void getConstructionXML(StringBuilder sb, boolean getListenersToo) { try { // save construction elements sb.append("<construction title=\""); StringUtil.encodeXML(sb, getTitle()); sb.append("\" author=\""); StringUtil.encodeXML(sb, getAuthor()); sb.append("\" date=\""); StringUtil.encodeXML(sb, getDate()); sb.append("\">\n"); // worksheet text if (worksheetTextDefined()) { sb.append("\t<worksheetText above=\""); StringUtil.encodeXML(sb, getWorksheetText(0)); sb.append("\" below=\""); StringUtil.encodeXML(sb, getWorksheetText(1)); sb.append("\"/>\n"); } getConstructionElementsXML(sb, getListenersToo); sb.append("</construction>\n"); } catch (Exception e) { e.printStackTrace(); } } /** * Appends minimal version of the construction XML to given string builder. * Only elements/commands are preserved, the rest is ignored. * * @param sb * String builder * @param getListenersToo * whether to includ JS listener names */ public void getConstructionElementsXML(StringBuilder sb, boolean getListenersToo) { ConstructionElement ce; int size = ceList.size(); for (int i = 0; i < size; ++i) { ce = ceList.get(i); ce.getXML(getListenersToo, sb); } } /** * Appends minimal version of the construction XML to given string builder. * OGP version. Only elements/commands are preserved, the rest is ignored. * * @param sb * String builder * @param statement * The statement to prove */ public void getConstructionElementsXML_OGP(StringBuilder sb, GeoElement statement) { ConstructionElement ce; int size = ceList.size(); for (int i = 0; i < size; ++i) { ce = ceList.get(i); if (!(ce instanceof AlgoProve) && !(ce instanceof AlgoProveDetails)) { // Collecting non-Prove* elements: ce.getXML_OGP(sb); } } // Inserting Prove* element: statement.getAlgorithmList().get(0).getXML_OGP(sb); } /** * Returns this construction in regression file .out format. * * @author Zoltan Kovacs <zoltan@geogebra.org> * * @param sb * string builder */ public void getConstructionRegressionOut(StringBuilder sb) { // change kernel settings temporarily StringTemplate tpl = StringTemplate.regression; try { ConstructionElement ce; int size = ceList.size(); for (int i = 0; i < size; ++i) { ce = ceList.get(i); sb.append(ce.getNameDescription()); sb.append(" = "); if (ce instanceof GeoElement) { // sb.append(((GeoElement) ce).toValueString()); ((GeoElement) ce).getXMLtagsMinimal(sb, tpl); } else if (ce instanceof AlgoElement) { sb.append(((AlgoElement) ce).getDefinition(tpl)); sb.append(" == "); sb.append(((AlgoElement) ce) .getAlgebraDescriptionRegrOut(tpl)); } sb.append("\n"); } } catch (Exception e) { sb.append(e.getMessage()); } } private boolean undoEnabled = true; /** * @return true if undo is enabled */ public boolean isUndoEnabled() { return undoEnabled; } /** * @param b * true to enable undo */ public void setUndoEnabled(boolean b) { undoEnabled = b; } /* * Construction List Management */ /** * Returns the ConstructionElement for the given construction index. * * @return the ConstructionElement for the given construction index. * @param index * Construction index of element to look for */ public ConstructionElement getConstructionElement(int index) { if (index < 0 || index >= ceList.size()) { return null; } return ceList.get(index); } /** * * @return first geo if exists */ public GeoElement getFirstGeo() { ConstructionElement ce = null; GeoElement geo = null; int index = 0; while (index < ceList.size() && geo == null) { ce = ceList.get(index); if (ce instanceof GeoElement) { geo = (GeoElement) ce; } index++; } return geo; } /** * Returns a set with all labeled GeoElement objects of this construction in * construction order. * * @return set with all labeled geos in construction order. */ final public TreeSet<GeoElement> getGeoSetConstructionOrder() { return geoSetConsOrder; } /** * Returns a set with all labeled GeoElement and all GeoCasCell objects of * this construction in construction order. * * @return set with all labeled geos and CAS cells in construction order. */ final public TreeSet<GeoElement> getGeoSetWithCasCellsConstructionOrder() { return geoSetWithCasCells; } /** * Returns a set with all labeled GeoElement objects of this construction in * alphabetical order of their labels. * * @return set with all labeled geos in alphabetical order. */ final public TreeSet<GeoElement> getGeoSetLabelOrder() { return geoSetLabelOrder; } /** * Starts to collect all redefinition calls for the current construction. * This is used to improve performance of many redefines in the spreadsheet * caused by e.g. relative copy. * * @see #processCollectedRedefineCalls() */ public void startCollectingRedefineCalls() { collectRedefineCalls = true; if (redefineMap == null) { redefineMap = new HashMap<GeoElement, GeoElement>(); } redefineMap.clear(); } /** * Stops collecting redefine calls. * * @see #processCollectedRedefineCalls() */ public void stopCollectingRedefineCalls() { collectRedefineCalls = false; if (redefineMap != null) { redefineMap.clear(); } } /** * Replaces oldGeo by newGeo in the current construction. This may change * the logic of the construction and is a very powerful operation * * @param oldGeo * Geo to be replaced. * @param newGeo * Geo to be used instead. * @throws Exception * i.e. for circular definition */ public void replace(GeoElement oldGeo, GeoElement newGeo) throws Exception { if (oldGeo == null || newGeo == null || oldGeo == newGeo) { return; } // assignment v=? should make v undefined, not change its type if (oldGeo.isIndependent() && newGeo instanceof GeoNumeric && newGeo.isIndependent() && !newGeo.isDefined()) { oldGeo.setUndefined(); oldGeo.updateRepaint(); return; } // Log.debug(oldGeo.getCommandDescription(StringTemplate.maxPrecision)+newGeo.getCommandDescription(StringTemplate.maxPrecision)); // if an object is redefined the same (eg in a script) rather than // reloading the whole XML, just update it if (oldGeo.getDefinition(StringTemplate.maxPrecision) .equals(newGeo.getDefinition(StringTemplate.maxPrecision)) && oldGeo.getParentAlgorithm() != null) { ArrayList<AlgoElement> ae = new ArrayList<AlgoElement>(); ae.add(oldGeo.getParentAlgorithm()); // make sure typing a=random() twice updates OK oldGeo.getParentAlgorithm().updateUnlabeledRandomGeos(); // make sure b=a+1 also updates AlgoElement.updateCascadeAlgos(ae); // repaint here to make sure #4114 is OK kernel.notifyRepaint(); return; } // if oldGeo does not have any children, we can simply // delete oldGeo and give newGeo the name of oldGeo if (!oldGeo.hasChildren()) { String oldGeoLabel = oldGeo.getLabelSimple(); newGeo.moveDependencies(oldGeo); isRemovingGeoToReplaceIt = true; oldGeo.remove(); isRemovingGeoToReplaceIt = false; // set properties first, set label later. See #933 newGeo.setAllVisualProperties(oldGeo, false); if (newGeo.isIndependent()) { addToConstructionList(newGeo, true); } else { AlgoElement parentAlgo = newGeo.getParentAlgorithm(); addToConstructionList(parentAlgo, true); // make sure all output objects get labels, see #218 GeoElement.setLabels(oldGeoLabel, parentAlgo.getOutput()); } // copy label of oldGeo to newGeo // use setLoadedLabel() instead of setLabel() to make sure that // hidden objects also get the label, see #379 newGeo.setLoadedLabel(oldGeoLabel); if (newGeo.isGeoText()) { newGeo.updateRepaint(); } return; } // check for circular definition if (newGeo.isChildOf(oldGeo)) { // check for eg a = a + 1, A = A + (1,1), a = !a if (oldGeo.isIndependent() && oldGeo instanceof GeoNumeric) { ((GeoNumeric) oldGeo) .setValue(((GeoNumeric) newGeo).getDouble()); oldGeo.updateRepaint(); newGeo.remove(); return; } else if (oldGeo.isIndependent() && oldGeo instanceof GeoPoint) { ((GeoPoint) oldGeo).set(newGeo); oldGeo.setDefinition(null); oldGeo.updateRepaint(); newGeo.remove(); return; } else if (oldGeo.isIndependent() && oldGeo instanceof GeoVector) { ((GeoVector) oldGeo).set(newGeo); oldGeo.setDefinition(null); oldGeo.updateRepaint(); newGeo.remove(); return; } else if (oldGeo.isIndependent() && oldGeo instanceof GeoBoolean) { ((GeoBoolean) oldGeo).set(newGeo); oldGeo.setDefinition(null); oldGeo.updateRepaint(); newGeo.remove(); return; } else if (oldGeo.isIndependent() && oldGeo.isGeoPoint() && oldGeo.isGeoElement3D()) {// GeoPoint3D oldGeo.set(newGeo); oldGeo.setDefinition(null); oldGeo.updateRepaint(); newGeo.remove(); return; } else { restoreCurrentUndoInfo(); throw new CircularDefinitionException(); } } // 1) remove all brothers and sisters of oldGeo // 2) move all predecessors of newGeo to the left of oldGeo in // construction list prepareReplace(oldGeo, newGeo); if (collectRedefineCalls) { // collecting redefine calls in redefineMap redefineMap.put(oldGeo, newGeo); return; } App app = kernel.getApplication(); // store views for plane app.getCompanion().storeViewCreators(); SelectionManager selection = kernel.getApplication() .getSelectionManager(); boolean moveMode = app.getMode() == EuclidianConstants.MODE_MOVE && selection.getSelectedGeos().size() > 0; String oldSelection = null; if (moveMode) { oldSelection = selection.getSelectedGeos().get(0) .getLabel(StringTemplate.defaultTemplate); } // get current construction XML isGettingXMLForReplace = true; StringBuilder consXML = getCurrentUndoXML(false); isGettingXMLForReplace = false; // 3) replace oldGeo by newGeo in XML doReplaceInXML(consXML, oldGeo, newGeo); // moveDependencies(oldGeo,newGeo); // 4) build new construction buildConstruction(consXML); if (moveMode) { GeoElement selGeo = kernel.lookupLabel(oldSelection); selection.addSelectedGeo(selGeo, false, true); app.getActiveEuclidianView().getEuclidianController() .handleMovedElement(selGeo, false, PointerEventType.MOUSE); } // recall views for plane app.getCompanion().recallViewCreators(); } private boolean isGettingXMLForReplace; /** * * @return true if is getting XML for replace */ public boolean isGettingXMLForReplace() { return isGettingXMLForReplace; } private boolean isRemovingGeoToReplaceIt = false; private boolean ignoringNewTypes; /** * * @return true if construction is removing an old geo to replace it (used * to prevent closing of object properties when replacing a single * geo) */ public boolean isRemovingGeoToReplaceIt() { return isRemovingGeoToReplaceIt; } /** * Processes all collected redefine calls as a batch to improve performance. * * @see #startCollectingRedefineCalls() * @throws Exception * i.e. for circular definition */ public void processCollectedRedefineCalls() throws Exception { collectRedefineCalls = false; if (redefineMap == null || redefineMap.size() == 0) { return; } // get current construction XML StringBuilder consXML = getCurrentUndoXML(false); // replace all oldGeo -> newGeo pairs in XML Iterator<Entry<GeoElement, GeoElement>> it = redefineMap.entrySet().iterator(); while (it.hasNext()) { Entry<GeoElement, GeoElement> entry = it.next(); GeoElement oldGeo = entry.getKey(); GeoElement newGeo = entry.getValue(); // 3) replace oldGeo by newGeo in XML doReplaceInXML(consXML, oldGeo, newGeo); } try { // 4) build new construction for all changes at once buildConstruction(consXML); } catch (Exception e) { throw e; } finally { stopCollectingRedefineCalls(); consXML.setLength(0); consXML = null; } } /** * Changes the given casCell taking care of necessary redefinitions. This * may change the logic of the construction and is a very powerful * operation. * * @param casCell * casCell to be changed * @throws Exception * in case of malformed XML */ public void changeCasCell(GeoCasCell casCell) throws Exception { setUpdateConstructionRunning(true); // move all predecessors of casCell to the left of casCell in // construction list updateConstructionOrder(casCell); // get current construction XML StringBuilder consXML = getCurrentUndoXML(false); // build new construction to make sure all ceIDs are correct after the // redefine buildConstruction(consXML); setUpdateConstructionRunning(false); } /** * Replaces oldGeo by newGeo in consXML. * * @param consXML * string builder * @param oldGeo * old element * @param newGeo * replacement */ protected void doReplaceInXML(StringBuilder consXML, GeoElement oldGeo, GeoElement newGeo) { String oldXML, newXML; // a = old string, b = new string AlgoElement oldGeoAlgo = oldGeo.getParentAlgorithm(); AlgoElement newGeoAlgo = newGeo.getParentAlgorithm(); GeoElement[] newGeoInputs = null; // change kernel settings temporarily // change kernel settings temporarily // set label to get replaceable XML if (newGeo.isLabelSet()) { // newGeo already exists in construction // oldGeo is replaced by newGeo, so oldGeo get's newGeo's label if (!oldGeo.getLabelSimple().equals(newGeo.getLabelSimple())) { oldGeo.setLabelSimple(newGeo.getLabelSimple()); // reload consXML to get the new name in the description of // dependent elements isGettingXMLForReplace = true; consXML.setLength(0); consXML.append(getCurrentUndoXML(false)); isGettingXMLForReplace = false; } oldXML = (oldGeoAlgo == null) ? oldGeo.getXML() : oldGeoAlgo.getXML(); newXML = ""; // remove oldGeo from construction } else { // newGeo doesn't exist in construction, so we take oldGeo's label newGeo.setLabelSimple(oldGeo.getLabelSimple()); newGeo.setLabelSet(true); // to get right XML output newGeo.setAllVisualProperties(oldGeo, false); newGeo.setViewFlags(oldGeo.getViewSet()); newGeo.setScripting(oldGeo); // NEAR-TO-RELATION for dependent new geo: // copy oldGeo's values to newGeo so that the // near-to-relationship can do its job if possible if (newGeoAlgo != null && newGeoAlgo.isNearToAlgorithm()) { try { newGeo.set(oldGeo); } catch (Exception e) { // do nothing } } isGettingXMLForReplace = true; oldXML = (oldGeoAlgo == null) ? oldGeo.getXML() : oldGeoAlgo.getXML(); if (newGeoAlgo == null) { newXML = newGeo.getXML(); } else { newXML = newGeoAlgo.getXML(); // get new geo inputs to check if we have to put the newXML // further in consXML newGeoInputs = newGeoAlgo.getInputForUpdateSetPropagation(); } isGettingXMLForReplace = false; // Application.debug("oldGeo: " + oldGeo + ", visible: " + // oldGeo.isEuclidianVisible() + ", algo: " + oldGeoAlgo); // Application.debug("newGeo: " + newGeo + ", visible: " + // newGeo.isEuclidianVisible() + ", algo: " + newGeoAlgo); } // restore old kernel settings // replace Strings: oldXML by newXML in consXML // Application.debug("cons=\n"+consXML+"\nold=\n"+oldXML+"\nnew=\n"+newXML); int pos = consXML.indexOf(oldXML); if (pos < 0) { restoreCurrentUndoInfo(); Log.debug("replace failed: oldXML string not found:\n" + oldXML); // Application.debug("consXML=\n" + consXML); throw new MyError(getApplication().getLocalization(), "ReplaceFailed"); } // System.out.println("REDEFINE: oldGeo: " + oldGeo + ", newGeo: " + // newGeo); // System.out.println(" old XML:\n" + consXML.substring(pos, pos + // oldXML.length())); // System.out.println(" new XML:\n" + newXML); // System.out.println("END redefine."); // get inputs position in consXML: we want to put new geo after that int inputEndPos = -1; if (newGeoInputs != null && newGeoInputs.length > 0) { int labelPos = 0; for (int i = 0; i < newGeoInputs.length; i++) { String label = newGeoInputs[i].getLabelSimple(); if (label != null) { int labelPos0 = consXML.indexOf("label=\"" + label + "\""); if (labelPos0 > labelPos) { labelPos = labelPos0; inputEndPos = consXML.indexOf("</element>", labelPos) + 11; } } } } // replace oldXML by newXML in consXML if (pos >= inputEndPos) { // old pos is ok consXML.replace(pos, pos + oldXML.length(), newXML); } else { // we put new geo after its inputs consXML.insert(inputEndPos, newXML); consXML.replace(pos, pos + oldXML.length(), ""); } } /** * Sets construction step position. Objects 0 to step in the construction * list will be visible in the views, objects step+1 to the end will be * hidden. * * @param s * : step number from range -1 ... steps()-1 where -1 shows an * empty construction. */ public void setStep(int s) { // Log.debug("setStep"+step+" "+s); Log.debug(step + " to" + s); if (s == step || s < -1 || s >= ceList.size()) { return; } kernel.setAllowVisibilitySideEffects(false); boolean cpara = kernel .isNotifyConstructionProtocolViewAboutAddRemoveActive(); kernel.setNotifyConstructionProtocolViewAboutAddRemoveActive(false); if (s < step) { Log.debug(step + " to" + s); // we must go from high to low there as otherwise the CAS cells // would // rearrange their numbers meanwhile for (int i = step; i >= s + 1; i--) { ceList.get(i).notifyRemove(); } } else { for (int i = step + 1; i <= s; ++i) { // Application.debug(i+""); ceList.get(i).notifyAdd(); } } kernel.setNotifyConstructionProtocolViewAboutAddRemoveActive(cpara); step = s; kernel.setAllowVisibilitySideEffects(true); // Michael Borcherds 2008-05-15 updateAllConstructionProtocolAlgorithms(); } /** * Returns current construction step position. * * @return current construction step position. */ public int getStep() { return step; } /* * GeoElementTable Management */ /** * Adds given GeoElement to a table where (label, object) pairs are stored. * * @param geo * GeoElement to be added, must be labeled * @see #removeLabel(GeoElement) * @see #lookupLabel(String) */ public void putLabel(GeoElement geo) { if (supressLabelCreation || geo.getLabelSimple() == null) { return; } geoTable.put(geo.getLabelSimple(), geo); addToGeoSets(geo); } /** * Removes given GeoElement from a table where (label, object) pairs are * stored. * * @param geo * GeoElement to be removed * @see #putLabel(GeoElement) */ public void removeLabel(GeoElement geo) { geoTable.remove(geo.getLabelSimple()); removeFromGeoSets(geo); } private void addToGeoSets(GeoElement geo) { geoSetConsOrder.add(geo); geoSetWithCasCells.add(geo); geoSetLabelOrder.add(geo); // get ordered type set GeoClass type = geo.getGeoClassType(); TreeSet<GeoElement> typeSet = geoSetsTypeMap.get(type); if (typeSet == null) { typeSet = createTypeSet(type); } typeSet.add(geo); /* * Application.debug("*** geoSet order (add " + geo + ") ***"); Iterator * it = geoSet.iterator(); * * while (it.hasNext()) { GeoElement g = (GeoElement) it.next(); * Application.debug(g.getConstructionIndex() + ": " + g); } */ } /** * Compares geos by labels (if set) * */ protected static class LabelComparator implements Comparator<GeoElement> { @Override public int compare(GeoElement ob1, GeoElement ob2) { GeoElement geo1 = ob1; GeoElement geo2 = ob2; return GeoElement.compareLabels(geo1.getLabelSimple(), geo2.getLabelSimple()); } } /** * Returns a set with all labeled GeoElement objects of a specific type in * alphabetical order of their labels. * * @param geoClassType * use {@link GeoClass} constants * @return Set of elements of given type. */ final public TreeSet<GeoElement> getGeoSetLabelOrder( GeoClass geoClassType) { TreeSet<GeoElement> typeSet = geoSetsTypeMap.get(geoClassType); if (typeSet == null) { typeSet = createTypeSet(geoClassType); } return typeSet; } private TreeSet<GeoElement> createTypeSet(GeoClass type) { TreeSet<GeoElement> typeSet = new TreeSet<GeoElement>( new LabelComparator()); geoSetsTypeMap.put(type, typeSet); return typeSet; } private void removeFromGeoSets(GeoElement geo) { geoSetConsOrder.remove(geo); geoSetWithCasCells.remove(geo); geoSetLabelOrder.remove(geo); // set ordered type set GeoClass type = geo.getGeoClassType(); TreeSet<GeoElement> typeSet = geoSetsTypeMap.get(type); if (typeSet != null) { typeSet.remove(geo); } /* * Application.debug("*** geoSet order (remove " + geo + ") ***"); * Iterator it = geoSet.iterator(); int i = 0; while (it.hasNext()) { * GeoElement g = (GeoElement) it.next(); * Application.debug(g.getConstructionIndex() + ": " + g); } */ } /** * Adds given GeoCasCell to a table where (label, object) pairs of CAS view * variables are stored. * * @param geoCasCell * GeoElement to be added, must have assignment variable * @param label * label for CAS cell * @see #removeCasCellLabel(String) * @see #lookupCasCellLabel(String) */ public void putCasCellLabel(GeoCasCell geoCasCell, String label) { if (label == null) { return; } if (geoCasCellTable == null) { geoCasCellTable = new HashMap<String, GeoCasCell>(); } geoCasCellTable.put(label, geoCasCell); } /** * Removes given GeoCasCell from the CAS variable table and from the * underlying CAS. * * @param variable * to be removed * @see #putCasCellLabel(GeoCasCell, String) */ public void removeCasCellLabel(String variable) { if (geoCasCellTable != null) { geoCasCellTable.remove(variable); } } /** * Returns a GeoElement for the given label. Note: only geos with * construction index 0 to step are available. * * @param label * label to be looked for * @return may return null */ public GeoElement lookupLabel(String label) { return lookupLabel(label, false); } /** * Returns a GeoCasCell for the given label. Note: only objects with * construction index 0 to step are available. * * @param label * to be looked for * @return may return null */ public GeoCasCell lookupCasCellLabel(String label) { GeoCasCell geoCasCell = null; // global var handling if (geoCasCellTable != null) { geoCasCell = geoCasCellTable.get(label); } // TODO add lookupCasCellLabel support for construction steps // // STANDARD CASE: variable name found // if (geoCasCell != null) { // return (GeoCasCell) checkConstructionStep(geoCasCell); // } return geoCasCell; } /** * Returns GeoCasCell referenced by given row label. * * @param label * row reference label, e.g. $5 for 5th row or $ for previous row * @return referenced row or null * @throws CASException * thrown if one or more row references are invalid (like $x or * if the number is higher than the number of rows) */ public GeoCasCell lookupCasRowReference(String label) throws CASException { if (!label .startsWith(ExpressionNodeConstants.CAS_ROW_REFERENCE_PREFIX)) { return null; } // $5 for 5th row int rowRef = -1; try { rowRef = Integer.parseInt(label.substring(1)); } catch (NumberFormatException e) { Log.error("Malformed CAS row reference: " + label); CASException ex = new CASException("CAS.InvalidReferenceError"); ex.setKey("CAS.InvalidReferenceError"); throw ex; } // we start to count at 0 internally but at 1 in the user interface GeoCasCell ret = getCasCell(rowRef - 1); if (ret == null) { Log.error("invalid CAS row reference: " + label); CASException ex = new CASException("CAS.InvalidReferenceError"); ex.setKey("CAS.InvalidReferenceError"); throw ex; } return ret; } /** * Returns a GeoElement for the given label. Note: only geos with * construction index 0 to step are available. * * @param label * to be looked for * @param allowAutoCreate * : true = allow automatic creation of missing labels (e.g. for * spreadsheet) * @return may return null */ public GeoElement lookupLabel(String label, boolean allowAutoCreate) {// package // private String label1 = label; if (label1 == null) { return null; } // local var handling if (localVariableTable != null) { GeoElement localGeo = localVariableTable.get(label1); if (localGeo != null) { return localGeo; } } // global var handling GeoElement geo = geoTableVarLookup(label1); // STANDARD CASE: variable name found if (geo != null) { return checkConstructionStep(geo); } // DESPARATE CASE: variable name not found /* * CAS VARIABLE HANDLING e.g. ggbtmpvara for a */ label1 = Kernel.removeCASVariablePrefix(label1); geo = geoTableVarLookup(label1); if (geo != null) { // geo found for name that starts with TMP_VARIABLE_PREFIX or // GGBCAS_VARIABLE_PREFIX return checkConstructionStep(geo); } /* * SPREADSHEET $ HANDLING In the spreadsheet we may have variable names * like "A$1" for the "A1" to deal with absolute references. Let's * remove all "$" signs from label and try again. */ if (label1.indexOf('$') > -1) { StringBuilder labelWithout$ = new StringBuilder( label1.length() - 1); for (int i = 0; i < label1.length(); i++) { char ch = label1.charAt(i); if (ch != '$') { labelWithout$.append(ch); } } String labelString = labelWithout$.toString(); // allow automatic creation of elements geo = lookupLabel(labelString, allowAutoCreate); if (geo != null) { // geo found for name that includes $ signs return checkConstructionStep(geo); } if (labelString.charAt(0) >= '0' && labelString.charAt(0) <= '9') { int cell = 0; try { cell = Integer.parseInt(labelWithout$.toString()); } catch (Exception e) { e.printStackTrace(); } if (cell > 0) { return this.getCasCell(cell - 1); } } } if ("self".equals(label1)) { return this.selfGeo; } if ("undefined".equals(label1)) { GeoNumeric n = new GeoNumeric(this); n.setUndefined(); return n; } if (!fileLoading) { if (label1.contains("_{")) { label1 = label1.replace("_{", "_"); if (label1.length() > 1) { label1 = label1.substring(0, label1.length() - 1); geo = geoTableVarLookup(label1); if (geo != null) { return checkConstructionStep(geo); } } } else if (label1.contains("_")) { label1 = label1.replace("_", "_{") + "}"; geo = geoTableVarLookup(label1); if (geo != null) { return checkConstructionStep(geo); } } } // try upper case version for spreadsheet label like a1 if (allowAutoCreate) { if (StringUtil.isLetter(label1.charAt(0)) // starts with letter && StringUtil.isDigit(label1.charAt(label1.length() - 1))) // ends // with // digit { String upperCaseLabel = label1.toUpperCase(); geo = geoTableVarLookup(upperCaseLabel); if (geo != null) { return checkConstructionStep(geo); } } } // look in CAS table too for label // needed for TRAC-2719; causes GGB-100 // geo = lookupCasCellLabel(label1); // if (geo != null) { // return geo; // } // if we get here, nothing worked: // possibly auto-create new GeoElement with that name if (allowAutoCreate) { return autoCreateGeoElement(label1); } return null; } /** * Search for constant with given label * * @param label * - label of constant * @return constant(GeoNumeric) from arbitraryConsTable with label */ public GeoNumeric lookupConstantLabel(String label) { if (!getArbitraryConsTable().isEmpty()) { for (MyArbitraryConstant arbConst : getArbitraryConsTable() .values()) { ArrayList<GeoNumeric> constList = arbConst.getConstList(); if (constList != null && !constList.isEmpty()) { for (GeoNumeric constant : constList) { if (constant.getLabelSimple().equals(label)) { return constant; } } } } } return null; } /** * Returns geo if it is available at the current construction step, * otherwise returns null. */ private GeoElement checkConstructionStep(GeoElement geo) { // check if geo is available for current step if (geo.isAvailableAtConstructionStep(step)) { return geo; } return null; } /** * Returns true if label is not occupied by any GeoElement including * GeoCasCells. * * @param label * label to be checked * @return true iff label is not occupied by any GeoElement. */ public boolean isFreeLabel(String label) { return isFreeLabel(label, true, false); } /** * Returns true if label is not occupied by any GeoElement. * * @param label * label to be checked * @param includeCASvariables * whether GeoCasCell labels should be checked too * @param includeDummies * when true, this method also checks that label is not used for * a CAS dummy * @return true iff label is not occupied by any GeoElement. */ public boolean isFreeLabel(String label, boolean includeCASvariables, boolean includeDummies) { if (label == null) { return false; } if (!fileLoading && getKernel().getApplication().getParserFunctions() .isReserved(label)) { return false; } if (fileLoading && casCellUpdate) { GeoNumeric geoNum = lookupConstantLabel(label); if (geoNum != null) { return false; } } if (fileLoading && !isCasCellUpdate() && geoTable.containsKey(label) && label.startsWith("c_")) { GeoElement geo = geoTable.get(label); if (geo instanceof GeoNumeric && !((GeoNumeric) geo).isDependentConst()) { return true; } return false; } if (fileLoading && !casCellUpdate && isNotXmlLoading()) { GeoNumeric geoNum = lookupConstantLabel(label); if (geoNum != null) { return false; } } if (!fileLoading && !casCellUpdate && label.startsWith("c_") && geoTable.containsKey(label)) { GeoElement geo = geoTable.get(label); if (geo instanceof GeoNumeric) { if (((GeoNumeric) geo).isDependentConst()) { return false; } return true; } } // check standard geoTable if (geoTable.containsKey(label)) { return false; } // optional: also check CAS variable table if (includeCASvariables && geoCasCellTable != null && geoCasCellTable.containsKey(label)) { return false; } if (includeDummies && casDummies.contains(label)) { return false; } return true; } /** * Moves all predecessors of newGeo (i.e. all objects that newGeo depends * upon) to the left of oldGeo in the construction list */ private void updateConstructionOrder(GeoElement oldGeo, GeoElement newGeo) { TreeSet<GeoElement> predSet = newGeo.getAllPredecessors(); // check if moving is needed // find max construction index of newGeo's predecessors and newGeo // itself int maxPredIndex = newGeo.getConstructionIndex(); for (GeoElement pred : predSet) { int predIndex = pred.getConstructionIndex(); if (predIndex > maxPredIndex) { maxPredIndex = predIndex; } } // no reordering is needed if (oldGeo.getConstructionIndex() > maxPredIndex) { return; } // reordering is needed // move all predecessors of newGeo (i.e. all objects that geo depends // upon) as far as possible to the left in the construction list for (GeoElement pred : predSet) { moveInConstructionList(pred, pred.getMinConstructionIndex()); } // move newGeo to the left as well (important if newGeo already existed // in construction) moveInConstructionList(newGeo, newGeo.getMinConstructionIndex()); // move oldGeo to its maximum construction index moveInConstructionList(oldGeo, oldGeo.getMaxConstructionIndex()); } /** * Makes sure that geoCasCell comes after all its predecessors in the * construction list. * * @param casCell * CAS cell * * @return whether construction list order was changed */ protected boolean updateConstructionOrder(GeoCasCell casCell) { // collect all predecessors of casCell TreeSet<GeoElement> allPred = new TreeSet<GeoElement>(); if (casCell.getGeoElementVariables() != null) { for (GeoElement directInput : casCell.getGeoElementVariables()) { allPred.addAll(directInput.getAllPredecessors()); allPred.add(directInput); } } if (allPred.size() == 0) { // there are no predecessors return false; // nothing changed } // Find max construction index of casCell's predecessors int maxPredIndex = 0; for (GeoElement pred : allPred) { int predIndex = pred.getConstructionIndex(); if (predIndex > maxPredIndex) { maxPredIndex = predIndex; } } // if casCell comes after all its new predecessors, // no reordering is needed if (casCell.getConstructionIndex() > maxPredIndex) { return false; } // reordering is needed // maybe we can move casCell down in the construction list int maxCellIndex = casCell.getMaxConstructionIndex(); if (maxCellIndex >= maxPredIndex) { moveInConstructionList(casCell, maxPredIndex + (maxCellIndex > maxPredIndex ? 1 : 0)); return true; } // reordering is needed but we cannot simply move down the casCell // because it has dependent objects: // move all predecessors of casCell up as far as possible maxPredIndex = 0; for (GeoElement pred : allPred) { moveInConstructionList(pred, pred.getMinConstructionIndex()); maxPredIndex = Math.max(maxPredIndex, pred.getConstructionIndex()); } // if casCell still comes before one of its predecessors // we have to move casCell if (casCell.getConstructionIndex() < maxPredIndex) { return true; } // maybe we can move casCell down in the construction list now if (casCell.getMaxConstructionIndex() > maxPredIndex) { moveInConstructionList(casCell, maxPredIndex + 1); return true; } System.err.println( "Construction.updateConstructionOrder(GeoCasCell) failed: " + casCell); return false; } // 1) remove all brothers and sisters of oldGeo // 2) move all predecessors of newGeo to the left of oldGeo in construction // list /** * @param oldGeo * old element * @param newGeo * replacement */ protected void prepareReplace(GeoElement oldGeo, GeoElement newGeo) { AlgoElement oldGeoAlgo = oldGeo.getParentAlgorithm(); AlgoElement newGeoAlgo = newGeo.getParentAlgorithm(); // 1) remove all brothers and sisters of oldGeo if (oldGeoAlgo != null) { keepGeo = oldGeo; oldGeoAlgo.removeOutputExcept(oldGeo); keepGeo = null; } // if newGeo is not in construction index, we must set its index now // in order to let (2) and (3) work if (newGeo.getConstructionIndex() == -1) { int ind = ceList.size(); if (newGeoAlgo == null) { newGeo.setConstructionIndex(ind); } else { newGeoAlgo.setConstructionIndex(ind); } } // make sure all output objects of newGeoAlgo are labeled, otherwise // we may end up with several objects that have the same label if (newGeoAlgo != null) { for (int i = 0; i < newGeoAlgo.getOutputLength(); i++) { GeoElement geo = newGeoAlgo.getOutput(i); if (geo != newGeo && geo.isDefined() && !geo.isLabelSet()) { geo.setLabel(null); // get free label } } } // 2) move all predecessors of newGeo to the left of oldGeo in // construction list updateConstructionOrder(oldGeo, newGeo); } /** * Adds the given GeoCasCell to a set with all labeled GeoElements and CAS * cells needed for notifyAll(). * * @param geoCasCell * CAS cell to be added */ public void addToGeoSetWithCasCells(GeoCasCell geoCasCell) { geoSetWithCasCells.add(geoCasCell); } /** * Removes the given GeoCasCell from a set with all labeled GeoElements and * CAS cells needed for notifyAll(). * * @param geoCasCell * CAS cell to be removed */ public void removeFromGeoSetWithCasCells(GeoCasCell geoCasCell) { geoSetWithCasCells.remove(geoCasCell); } /** * Creates a new GeoElement for the spreadsheet of same type as * neighbourCell. * * @return new GeoElement of desired type * @param neighbourCell * another geo of the desired type * @param label * Label for the new geo */ final public GeoElement createSpreadsheetGeoElement( GeoElement neighbourCell, String label) { GeoElement result; // found neighbouring cell: create geo of same type if (neighbourCell != null) { result = neighbourCell.copy(); } // no neighbouring cell: create number with value 0 else { result = new GeoNumeric(this); } // set result as empty cell geo result.setUndefined(); result.setEmptySpreadsheetCell(true); // make sure that label creation is turned on boolean oldSuppressLabelsActive = isSuppressLabelsActive(); setSuppressLabelCreation(false); // set 0 and label // result.setZero(); result.setAuxiliaryObject(true); result.setLabel(label); // revert to previous label creation state setSuppressLabelCreation(oldSuppressLabelsActive); return result; } /** * Returns the next free indexed label using the given prefix starting with * the given index number. * * @param prefix * e.g. "c" * @param startIndex * e.g. 2 * @return indexed label, e.g. "c_2" */ public String getIndexLabel(String prefix, int startIndex) { // start numbering with indices using suggestedLabel // as prefix String pref; int pos = prefix.indexOf('_'); if (pos == -1) { pref = prefix; } else { pref = prefix.substring(0, pos); } StringBuilder sbIndexLabel = new StringBuilder(); StringBuilder sbLongIndexLabel = new StringBuilder(); int n = startIndex; // int n = 1; // start index // if (startIndex != null) { // try { // n = Integer.parseInt(startIndex); // } catch (NumberFormatException e) { // n = 1; // } // } do { sbIndexLabel.setLength(0); sbLongIndexLabel.setLength(0); sbLongIndexLabel.append(pref); sbLongIndexLabel.append("_{"); sbLongIndexLabel.append(n); sbLongIndexLabel.append('}'); // n as index if (n < 10) { sbIndexLabel.setLength(0); sbIndexLabel.append(pref); sbIndexLabel.append('_'); sbIndexLabel.append(n); } else { sbIndexLabel.append(sbLongIndexLabel); } n++; } while (!isFreeLabel(sbIndexLabel.toString()) || !isFreeLabel(sbLongIndexLabel.toString())); return sbIndexLabel.toString(); } /** * Returns the next free indexed label using the given prefix. * * @param prefix * e.g. "c" * @return indexed label, e.g. "c_2" */ public String getIndexLabel(String prefix) { return getIndexLabel(prefix, 1); } /** * Automatically creates a GeoElement object for a certain label that is not * yet used in the geoTable of this construction. This is done for e.g. * point i = (0,1), number e = Math.E, empty spreadsheet cells * * @param labelNew * label for new element, may not be null * @return created element */ protected GeoElement autoCreateGeoElement(String labelNew) { GeoElementND createdGeo = null; boolean fix = true; boolean auxilliary = true; String label = labelNew; int length = label.length(); // expression like AB, autocreate AB=Distance[A,B] or AB = A * B // according to whether A,B are points or numbers if (length == 3 && label.charAt(2) == '\'') { createdGeo = distanceOrProduct(label.charAt(0) + "", label.charAt(1) + "'"); fix = false; } else if (length == 3 && label.charAt(1) == '\'') { createdGeo = distanceOrProduct(label.charAt(0) + "'", label.charAt(2) + ""); fix = false; } else if (length == 4 && label.charAt(1) == '\'' && label.charAt(3) == '\'') { createdGeo = distanceOrProduct(label.charAt(0) + "'", label.charAt(2) + "'"); fix = false; } else if (length == 2) { createdGeo = distanceOrProduct(label.charAt(0) + "", label.charAt(1) + ""); fix = false; } else if (length == 1) { if ("O".equals(label)) { createdGeo = new GeoPoint(this, 0d, 0d, 1d); label = "O"; auxilliary = true; fix = true; } } // handle i or e case if (createdGeo != null) { // removed: not needed for e,i and causes bug with using Circle[D, // CD 2] in locus // boolean oldSuppressLabelsActive = isSuppressLabelsActive(); // setSuppressLabelCreation(false); createdGeo.setAuxiliaryObject(auxilliary); createdGeo.setLabel(label); createdGeo.setFixed(fix); // revert to previous label creation state // setSuppressLabelCreation(oldSuppressLabelsActive); return createdGeo.toGeoElement(); } // check spreadsheet cells // for missing spreadsheet cells, create object // of same type as above createdGeo = GeoElementSpreadsheet.autoCreate(label, this); if (createdGeo == null) { return null; } return createdGeo.toGeoElement(); } private GeoNumberValue distanceOrProduct(String string, String string2) { GeoElement geo1 = kernel.lookupLabel(string); if (geo1 != null && geo1.isGeoPoint()) { GeoElement geo2 = kernel.lookupLabel(string2); if (geo2 != null && geo2.isGeoPoint()) { AlgoDistancePoints dist = new AlgoDistancePoints(this, (GeoPointND) geo1, (GeoPointND) geo2); return dist.getDistance(); } } else if (geo1 instanceof NumberValue) { GeoElement geo2 = kernel.lookupLabel(string2); if (geo2 instanceof NumberValue) { ExpressionNode node = new ExpressionNode(kernel, geo1, Operation.MULTIPLY, geo2); AlgoDependentNumber algo = new AlgoDependentNumber(this, node, false); return algo.getNumber(); } } return null; } /** * Make geoTable contain only xAxis and yAxis */ final private void initGeoTables() { geoTable.clear(); geoCasCellTable = null; localVariableTable = null; constsM.clear(); complexNumbersM.clear(); intsM.clear(); // add axes labels both in English and current language geoTable.put("xAxis", xAxis); geoTable.put("yAxis", yAxis); usedGeos.clear(); if (xAxisLocalName != null) { geoTable.put(xAxisLocalName, xAxis); geoTable.put(yAxisLocalName, yAxis); } companion.initGeoTables(); } /** * @param b * flag to ignore new types (for creating default geos) */ public void setIgnoringNewTypes(boolean b) { this.ignoringNewTypes = b; } /** * @param c * used class of element (needed to decide about 2D * compatibility) */ public void addUsedType(GeoClass c) { if (this.ignoringNewTypes) { return; } this.usedGeos.add(c); } /** * @return whether there are some objects incompatible with the 2D version */ public boolean has3DObjects() { Iterator<GeoClass> it = usedGeos.iterator(); boolean kernelHas3DObjects = false; while (it.hasNext()) { GeoClass geoType = it.next(); if (geoType.is3D) { Log.debug("found 3D geo: " + geoType.xmlName); kernelHas3DObjects = true; break; } } return kernelHas3DObjects; } /** * @return Whether some objects were created in this cons */ public boolean isStarted() { return usedGeos.size() > 0 || kernel.getMacroNumber() > 0; } /** * Returns a set with all labeled GeoElement objects sorted in alphabetical * order of their type strings and labels (e.g. Line g, Line h, Point A, * Point B, ...). Note: the returned TreeSet is a copy of the current * situation and is not updated by the construction later on. * * @return Set of all labeld GeoElements orted by name and description */ final public TreeSet<GeoElement> getGeoSetNameDescriptionOrder() { // sorted set of geos TreeSet<GeoElement> sortedSet = new TreeSet<GeoElement>( new NameDescriptionComparator()); // get all GeoElements from construction and sort them Iterator<GeoElement> it = geoSetConsOrder.iterator(); while (it.hasNext()) { GeoElement geo = it.next(); // sorted inserting using name description of geo sortedSet.add(geo); } return sortedSet; } /** * Returns extremum finder * * @return extremum finder */ public ExtremumFinderI getExtremumFinder() { return kernel.getExtremumFinder(); } /* * redo / undo */ /** * Stores current state of construction. * * @see UndoManager#storeUndoInfo */ public void storeUndoInfo() { // undo unavailable in applets // if (getApplication().isApplet()) return; if (!isUndoEnabled()) { return; } undoManager.storeUndoInfo(); } /** * Restores undo info * * @see UndoManager#restoreCurrentUndoInfo() */ public void restoreCurrentUndoInfo() { // undo unavailable in applets // if (getApplication().isApplet()) return; collectRedefineCalls = false; if (undoManager != null) { undoManager.restoreCurrentUndoInfo(); } } /** * Redoes last undone step */ public void redo() { // undo unavailable in applets // if (getApplication().isApplet()) return; undoManager.redo(); } /** * Undoes last operation */ public void undo() { // undo unavailable in applets // if (getApplication().isApplet()) return; undoManager.undo(); } /** * Returns true iff undo is possible * * @return true iff undo is possible */ public boolean undoPossible() { // undo unavailable in applets // if (getApplication().isApplet()) return false; return undoManager != null && undoManager.undoPossible(); } /** * Returns true iff redo is possible * * @return true iff redo is possible */ public boolean redoPossible() { // undo unavailable in applets // if (getApplication().isApplet()) return false; return undoManager != null && undoManager.redoPossible(); } /** * Add a macro to list of used macros * * @param macro * Macro to be added */ public final void addUsedMacro(Macro macro) { if (usedMacros == null) { usedMacros = new ArrayList<Macro>(); } usedMacros.add(macro); } /** * Returns list of macros used in this construction * * @return list of macros used in this construction */ public ArrayList<Macro> getUsedMacros() { return usedMacros; } /** * Calls remove() for every ConstructionElement in the construction list. * After this the construction list will be empty. */ public void clearConstruction() { constsM.clear(); complexNumbersM.clear(); intsM.clear(); ceList.clear(); algoList.clear(); geoSetConsOrder.clear(); geoSetWithCasCells.clear(); geoSetLabelOrder.clear(); geoSetsTypeMap.clear(); euclidianViewCE.clear(); this.corner5Algos = null; this.corner11Algos = null; this.casDummies.clear(); initGeoTables(); // reinit construction step step = -1; // delete title, author, date title = null; author = null; date = null; worksheetText[0] = null; worksheetText[1] = null; usedMacros = null; spreadsheetTraces = false; } /** * Returns undo xml string of this construction. * * @param getListenersToo * whether to include JS listeners * * @return StringBuilder with xml of this construction. */ public StringBuilder getCurrentUndoXML(boolean getListenersToo) { return MyXMLio.getUndoXML(this, getListenersToo); } /** * Each construction has its own IO because of strong coupling between * those. * * @return MyXMLio for this construction */ public MyXMLio getXMLio() { if (xmlio == null) { xmlio = kernel.getApplication().createXMLio(this); } return xmlio; } private MyXMLio xmlio; private GeoElement outputGeo; /** * Clears the undo info list of this construction and adds the current * construction state to the undo info list. */ public void initUndoInfo() { ensureUndoManagerExists(); undoManager.initUndoInfo(); } /** * Tries to build the new construction from the given XML string. */ private void buildConstruction(StringBuilder consXML) throws Exception { // try to process the new construction try { ensureUndoManagerExists(); undoManager.processXML(consXML.toString()); kernel.notifyReset(); // Update construction is done during parsing XML // kernel.updateConstruction(); } catch (Exception e) { restoreCurrentUndoInfo(); throw e; } catch (MyError err) { restoreCurrentUndoInfo(); throw err; } } /** * process xml to create construction * * @param xml * XML builder */ public void processXML(StringBuilder xml) { try { undoManager.processXML(xml.toString()); } catch (Exception e) { e.printStackTrace(); } } /** * Returns the UndoManager (for Copy & Paste) * * @return UndoManager */ public UndoManager getUndoManager() { ensureUndoManagerExists(); return undoManager; } private void ensureUndoManagerExists() { if (undoManager == null) { undoManager = kernel.getApplication().getUndoManager(this); } } /** * used by commands Element[] and Cell[] as they need to know their output * type in advance * * @param type * type generated by getXMLTypeString() */ public void setOutputGeo(String type) { if (type == null) { this.outputGeo = null; return; } this.outputGeo = kernel.createGeoElement(this, type); } /** * used by commands Element[] and Cell[] as they need to know their output * type in advance default: return new GeoNumeric(this) * * @return output of command currently parsed from XML */ public GeoElement getOutputGeo() { return outputGeo == null ? new GeoNumeric(this) : outputGeo; } private TreeSet<String> registredFV = new TreeSet<String>(); /** * Registers function variable that should be recognized in If and Function * commands * * @param fv * local function variable */ public void registerFunctionVariable(String fv) { if (fv == null) { registredFV.clear(); } else { registredFV.add(fv); } } /** * * @param s * variable name * @return whether s is among registred function variables */ public boolean isRegistredFunctionVariable(String s) { return registredFV.contains(s); } /** * Returns function variable that should be recognized in If and Function * commands * * @return local function variable or null if there is none */ public String getRegisteredFunctionVariable() { Iterator<String> it = registredFV.iterator(); if (it.hasNext()) { return it.next(); } return null; } private boolean fileLoading; private boolean casCellUpdate = false; private boolean notXmlLoading = false; private boolean updateConstructionRunning; /** * Let construction know about file being loaded. When this is true, user * defined objects called sin, cos, ... are accepted * * @param b * true if file is loading */ public void setFileLoading(boolean b) { fileLoading = b; } /** * @return whether we are just loading a file */ public boolean isFileLoading() { return fileLoading; } /** * @param b * true if cas cell is updated */ public void setCasCellUpdate(boolean b) { casCellUpdate = b; } /** * @return whether we have cas cell update */ public boolean isCasCellUpdate() { return casCellUpdate; } /** * @return whether we need to create a new arbitrary constant and it's not * read from xml */ public boolean isNotXmlLoading() { return notXmlLoading; } /** * it is called0 in MyArbitraryConstant * * @param b * - false if constant is created by xml reading, true if * constant is created by MyArbitraryConstant */ public void setNotXmlLoading(boolean b) { this.notXmlLoading = b; } // update all indices >= pos /** * @return whether updateConstruction is running */ public boolean isUpdateConstructionRunning() { return updateConstructionRunning; } private final TreeSet<String> casDummies = new TreeSet<String>(); /** * @return set of names that are used by CAS for dummies */ public TreeSet<String> getCASdummies() { return casDummies; } /** * TODO place this JavaDoc to the correct spot Build a set with all * algorithms of this construction (in topological order). The method * updateAll() of this set can be used to update the whole construction. * * public AlgorithmSet buildOveralAlgorithmSet() { // 1) get all independent * GeoElements in construction and update them // 2) build one overall * updateSet from all updateSets of (1) * * // 1) get all independent geos in construction LinkedHashSet indGeos = * new LinkedHashSet(); int size = ceList.size(); for (int i = 0; i < size; * ++i) { ConstructionElement ce = (ConstructionElement) ceList.get(i); if * (ce.isIndependent()) indGeos.add(ce); else { * indGeos.addAll(ce.getAllIndependentPredecessors()); } } * * // 2) build one overall updateSet AlgorithmSet algoSet = new * AlgorithmSet(); Iterator it = indGeos.iterator(); while (it.hasNext()) { * GeoElement geo = (GeoElement) it.next(); * * // update this geo only geo.update(); * * // get its update set and add it to the overall updateSet * algoSet.addAll(geo.getAlgoUpdateSet()); } * * return algoSet; } */ /** algo set currently updated by GeoElement.updateDependentObjects() */ private AlgorithmSet algoSetCurrentlyUpdated; private boolean spreadsheetTraces; private boolean allowUnboundedAngles = true; /** * set the algo set currently updated by GeoElement.updateDependentObjects() * * @param algoSetCurrentlyUpdated * algo set */ public void setAlgoSetCurrentlyUpdated( AlgorithmSet algoSetCurrentlyUpdated) { this.algoSetCurrentlyUpdated = algoSetCurrentlyUpdated; } /** * * @return the algo set currently updated by * GeoElement.updateDependentObjects() */ public AlgorithmSet getAlgoSetCurrentlyUpdated() { return algoSetCurrentlyUpdated; } /** * @param b * new value of update construction flag */ public void setUpdateConstructionRunning(boolean b) { updateConstructionRunning = b; } /** * @return a copy of the set of all geo labels that are currently being used */ public Set<String> getAllGeoLabels() { return new HashSet<String>(geoTable.keySet()); } /** * @return a copy of the set of all labels that are currently being used */ public Set<String> getAllLabels() { Set<String> ret = new HashSet<String>(getAllGeoLabels()); if (geoCasCellTable != null) { ret.addAll(geoCasCellTable.keySet()); } return ret; } /** * @return whether some geos have activated spreadsheet trace */ public boolean hasSpreadsheetTracingGeos() { return spreadsheetTraces; } /** * Notify the construction about a geo with spreadsheet tracing */ public void addTracingGeo() { spreadsheetTraces = true; } /** * @param allow * whether unbounded angles are allowed */ public void setAllowUnboundedAngles(boolean allow) { this.allowUnboundedAngles = allow; } /** * @return whether unbounded angles are allowed on file load */ public boolean isAllowUnboundedAngles() { return this.allowUnboundedAngles; } private ArrayList<AlgoElement> casAlgos = new ArrayList<AlgoElement>(); /** * Add algo to a list of algos that need update after CAS load * * @param casAlgo * algo using CAS */ public void addCASAlgo(AlgoElement casAlgo) { casAlgos.add(casAlgo); } /** * Recompute all algos using CASS and dependent CAS cells */ public void recomputeCASalgos() { for (AlgoElement algo : casAlgos) { if (algo.getOutput() != null && !algo.getOutput(0).isLabelSet()) { if (algo instanceof AlgoCasBase) { ((AlgoCasBase) algo).clearCasEvalMap(""); algo.compute(); } else if (algo instanceof AlgoUsingTempCASalgo) { ((AlgoUsingTempCASalgo) algo).refreshCASResults(); algo.compute(); } else if (algo instanceof UsesCAS || algo instanceof AlgoCasCellInterface) { // eg Limit, LimitAbove, LimitBelow, SolveODE // AlgoCasCellInterface: eg Solve[x^2] algo.compute(); } algo.getOutput(0).updateCascade(); } } casAlgos.clear(); } /** * Update construction after language change (affects Name[] and similar * algos) */ public void updateConstructionLanguage() { // collect notifyUpdate calls using xAxis as dummy geo updateConstructionRunning = true; boolean oldFlag = this.kernel.getApplication().isBlockUpdateScripts(); this.kernel.getApplication().setBlockUpdateScripts(true); try { // G.Sturr 2010-5-28: turned this off so that random numbers can be // traced // if (!kernel.isMacroKernel() && kernel.app.hasGuiManager()) // kernel.app.getGuiManager().startCollectingSpreadsheetTraces(); // update all independent GeoElements int size = ceList.size(); for (int i = 0; i < size; ++i) { ConstructionElement ce = ceList.get(i); if (ce.isGeoElement()) { if (((GeoElement) ce).isGeoText() && ((GeoElement) ce).getParentAlgorithm() != null) { ((GeoElement) ce).getParentAlgorithm().update(); } ce.update(); } } } finally { this.kernel.getApplication().setBlockUpdateScripts(oldFlag); updateConstructionRunning = false; } } /** TODO can we kill this now that we don't use MQ? */ public void updateConstructionLaTeX() { boolean oldFlag = this.kernel.getApplication().isBlockUpdateScripts(); this.kernel.getApplication().setBlockUpdateScripts(true); // TODO we do not need the whole construction update here if (latexGeos != null) { GeoElement.updateCascade(latexGeos, new TreeSet<AlgoElement>(), true); } this.latexGeos = null; this.kernel.getApplication().setBlockUpdateScripts(oldFlag); } /** * * @param algo * algo dependent on view pixel size */ public void registerCorner5(EuclidianViewCE algo) { if (this.corner5Algos == null) { this.corner5Algos = new ArrayList<EuclidianViewCE>(); } this.corner5Algos.add(algo); } /** * * @param algo * algo dependent on rotation of 3D view */ public void registerCorner11(EuclidianViewCE algo) { if (this.corner11Algos == null) { this.corner11Algos = new ArrayList<EuclidianViewCE>(); } this.corner11Algos.add(algo); } /** * @return all function variables registered for parsing */ public String[] getRegisteredFunctionVariables() { String[] varNames = new String[this.registredFV.size()]; Iterator<String> it = this.registredFV.iterator(); int i = 0; while (it.hasNext()) { varNames[i++] = it.next(); } return varNames; } /** * @param geo * element using LaTeX */ public void addLaTeXGeo(GeoElement geo) { if (latexGeos == null) { latexGeos = new ArrayList<GeoElement>(); } this.latexGeos.add(geo); } /** * @return number of CAS cells */ public int getCASObjectNumber() { int counter = 0; for (ConstructionElement ce : ceList) { if (ce instanceof GeoCasCell) { ++counter; } else if (ce instanceof AlgoCasCellInterface) { ++counter; } } return counter; } /** * @param A * - start point of segment * @param B * - end point of segment * @return segment defined by A and B */ public GeoSegment getSegmentFromAlgoList(GeoPoint A, GeoPoint B) { if (!algoList.isEmpty()) { Iterator<AlgoElement> it = algoList.iterator(); while (it.hasNext()) { AlgoElement curr = it.next(); if (curr instanceof AlgoJoinPointsSegment) { if ((curr.getInput(0).equals(A) && curr.getInput(1).equals(B)) || (curr.getInput(0).equals(B) && curr.getInput(1).equals(A))) { return ((AlgoJoinPointsSegment) curr).getSegment(); } } } } return null; } /** * @return z-axis */ final public GeoAxisND getZAxis() { return companion.getZAxis(); } /** * @return plane z=0 */ final public GeoDirectionND getXOYPlane() { return companion.getXOYPlane(); } /** * @return space placeholder */ final public GeoDirectionND getSpace() { return companion.getSpace(); } /** * @return clipping cube */ final public GeoElement getClippingCube() { return companion.getClippingCube(); } /** * @return map label => geo */ public HashMap<String, GeoElement> getGeoTable() { return geoTable; } /** * @return whether this is a 3D instance */ public boolean is3D() { return companion.is3D(); } /** * @return next construction element ID */ public long getNextCeIDcounter() { return ceIDcounter++; } /** * @return next prover variable ID */ public int getNextVariableID() { return nextVariableID++; } }