/*
GeoGebra - Dynamic Mathematics for Everyone
http://www.geogebra.org
This file is part of GeoGebra.
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation.
*/
package org.geogebra.common.kernel.algos;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.TreeSet;
import org.geogebra.common.euclidian.EuclidianConstants;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.FixedPathRegionAlgo;
import org.geogebra.common.kernel.Locateable;
import org.geogebra.common.kernel.Macro;
import org.geogebra.common.kernel.Path;
import org.geogebra.common.kernel.PathParameter;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.arithmetic.ExpressionNode;
import org.geogebra.common.kernel.arithmetic.ExpressionValue;
import org.geogebra.common.kernel.arithmetic.FunctionNVar;
import org.geogebra.common.kernel.geos.GeoAngle;
import org.geogebra.common.kernel.geos.GeoConic;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoFunction;
import org.geogebra.common.kernel.geos.GeoLine;
import org.geogebra.common.kernel.geos.GeoList;
import org.geogebra.common.kernel.geos.GeoNumeric;
import org.geogebra.common.kernel.geos.GeoPoint;
import org.geogebra.common.kernel.geos.GeoPolygon;
import org.geogebra.common.kernel.geos.GeoVector;
import org.geogebra.common.kernel.kernelND.GeoElementND;
import org.geogebra.common.kernel.kernelND.GeoPointND;
import org.geogebra.common.util.debug.Log;
/**
* Algorithm to invoke a specific macro.
*
* @author Markus
*/
public class AlgoMacro extends AlgoElement
implements AlgoMacroInterface, FixedPathRegionAlgo {
private Macro macro;
// macro construction, its input and output used by this algo
private GeoElement[] macroInput, macroOutput;
// maps macro geos to algo geos
private HashMap<GeoElementND, GeoElement> macroToAlgoMap;
// all keys of macroToAlgoMap that are not part of macroInput
private ArrayList<GeoElementND> macroOutputAndReferencedGeos;
private ArrayList<GeoElementND> algoOutputAndReferencedGeos; // for
// efficiency,
// see
// getMacroConstructionState()
private boolean locked;
/**
* Creates a new algorithm that applies a macro to the given input objects.
*
* @param cons
* construction
* @param labels
* output labels
* @param macro
* macro
* @param input
* input objects
*/
public AlgoMacro(Construction cons, String[] labels, Macro macro,
GeoElement[] input, boolean add) {
super(cons, add);
this.input = input;
this.macro = macro;
this.macroInput = macro.getMacroInput();
this.macroOutput = macro.getMacroOutput();
// register algorithm with macro
macro.registerAlgorithm(this);
// create copies for the output objects
createOutputObjects();
// initialize the mapping between macro geos and algo geos
initMap();
setInputOutput();
compute();
// check if macro construction has euclidianAlgos
if (macro.getMacroConstruction().hasEuclidianViewCE()) {
cons.registerEuclidianViewCE(this);
}
if (add) {
GeoElement.setLabels(labels, getOutput());
// we hide objects that are hidden in macro construction, but
// we want to do this only with 4.0 macros
if (macro.isCopyCaptionsAndVisibility()) {
for (int i = 0; i < macroOutput.length; i++) {
if (!macroOutput[i].isSetEuclidianVisible()) {
getOutput(i).setEuclidianVisible(false);
getOutput(i).update();
}
}
} else {
// for <=3.2 macros hide all angles
for (int i = 0; i < macroOutput.length; i++) {
if (macroOutput[i] instanceof GeoAngle) {
getOutput(i).setEuclidianVisible(false);
getOutput(i).update();
}
}
}
}
}
@Override
public void remove() {
if (removed) {
return;
}
macro.unregisterAlgorithm(this);
super.remove();
}
@Override
public Algos getClassName() {
return Algos.AlgoMacro;
}
@Override
public String getDefinitionName(StringTemplate tpl) {
return macro.getCommandName();
}
@Override
protected void setInputOutput() {
setDependencies();
}
/**
* The CopyPaste class needs a list of used macros to work perfectly
*
* @return Macro macro
*/
public Macro getMacro() {
return macro;
}
@Override
final public void compute() {
try {
// set macro geos to algo geos state
setMacroConstructionState();
// update all algorithms of macro-construction
macro.getMacroConstruction().updateAllAlgorithms();
boolean pointsChanged = false;
for (int i = 0; i < macroOutput.length; i++) {
GeoElement geoPoint = macroOutput[i];
if (geoPoint.isPointOnPath()) {
GeoPointND P = (GeoPointND) getOutput(i);
double t = P.getPathParameter().getT();
Path path = ((GeoPointND) geoPoint).getPath();
PathParameter pp = ((GeoPointND) geoPoint)
.getPathParameter();
// Application.debug(param.getDouble()+"
// "+path.getMinParameter()+" "+path.getMaxParameter());
pp.setT(t);
// Application.debug(pp.t);
path.pathChanged(P);
P.updateCoords();
pointsChanged = true;
}
}
if (pointsChanged) {
macro.getMacroConstruction().updateAllAlgorithms();
}
// set algo geos to macro geos state
getMacroConstructionState();
} catch (Exception e) {
Log.debug("AlgoMacro compute():\n");
this.locked = false;
e.printStackTrace();
for (int i = 0; i < getOutputLength(); i++) {
getOutput(i).setUndefined();
}
}
}
/**
* Returns true when macroGeo is part of macroInput.
*/
private boolean isMacroInputObject(GeoElementND macroGeo) {
for (int i = 0; i < macroInput.length; i++) {
if (macroGeo == macroInput[i]) {
return true;
}
}
return false;
}
/**
* Sets macro geos to the current state of algo geos. Start points of
* vectors should not be copied.
*/
final void setMacroConstructionState() {
// set input objects of macro construction
for (int i = 0; i < macroInput.length; i++) {
macroInput[i].set(input[i]);
try {
if (macroInput[i] instanceof GeoVector) {
((GeoVector) macroInput[i]).setStartPoint(null);
}
} catch (Exception e) {
Log.debug("Exception while handling vector input: " + e);
}
macroInput[i].setRealLabel(input[i].getLabelSimple());
// Application.debug("SET INPUT object: " + input[i] + " => " +
// macroInput[i]);
}
}
/**
* Sets algo geos to the current state of macro geos.
*/
final void getMacroConstructionState() {
this.locked = true;
// for efficiency: instead of lookups in macroToAlgoMap
// we use an array list algoOutputAndReferencedGeos with corresponding
// macro and algo geos
int size = macroOutputAndReferencedGeos.size();
for (int i = 0; i < size; i++) {
GeoElementND macroGeo = macroOutputAndReferencedGeos.get(i);
GeoElementND algoGeo = algoOutputAndReferencedGeos.get(i);
if (macroGeo.isDefined()) {
algoGeo.set(macroGeo);
AlgoElement drawAlgo = macroGeo.getParentAlgorithm();
if (macro.isCopyCaptionsAndVisibility()) {
algoGeo.setAdvancedVisualStyleCopy(macroGeo);
}
boolean oldVisible = algoGeo.isSetEuclidianVisible();
if (drawAlgo instanceof DrawInformationAlgo) {
((GeoNumeric) algoGeo).setDrawable(true, oldVisible);
algoGeo.setDrawAlgorithm(
((DrawInformationAlgo) drawAlgo).copy());
}
} else {
algoGeo.setUndefined();
}
}
this.locked = false;
}
/**
* Creates the output objects of this macro algorithm
*/
private void createOutputObjects() {
setOutputLength(macroOutput.length);
int layer = kernel.getApplication().getMaxLayerUsed();
for (int i = 0; i < macroOutput.length; i++) {
// copy output object of macro and make the copy part of this
// construction
setOutput(i, macroOutput[i].copyInternal(cons));
GeoElement out = getOutput(i);
out.setUseVisualDefaults(false);
out.setVisualStyle(macroOutput[i]);
// set layer
out.setLayer(macroOutput[i].getLayer());
out.setAdvancedVisualStyleCopy(macroOutput[i]);
if (macro.isCopyCaptionsAndVisibility()) {
out.setCaption(macroOutput[i].getRawCaption());
}
out.setLayer(layer);
AlgoElement drawAlgo = macroOutput[i].getParentAlgorithm();
if (drawAlgo instanceof DrawInformationAlgo) {
((GeoNumeric) out).setDrawable(true);
out.setDrawAlgorithm(((DrawInformationAlgo) drawAlgo).copy());
}
out.setAlgoMacroOutput(true);
}
}
/**
* Inits the mapping of macro geos to algo geos construction. The map is
* used to set and get the state of the macro construction in compute() and
* to make sure that all output geos of the algorithm and all their
* references (e.g. the start point of a ray) are part of the algorithm's
* construction.
*/
private void initMap() {
macroToAlgoMap = new HashMap<GeoElementND, GeoElement>();
macroOutputAndReferencedGeos = new ArrayList<GeoElementND>();
algoOutputAndReferencedGeos = new ArrayList<GeoElementND>();
// INPUT initing
// map macro input to algo input
for (int i = 0; i < macroInput.length; i++) {
map(macroInput[i], input[i]);
}
// OUTPUT initing
// map macro output to algo output
for (int i = 0; i < macroOutput.length; i++) {
map(macroOutput[i], getOutput(i));
}
// SPECIAL REFERENCES of output
// make sure all algo-output objects reference objects in their own
// construction
// note: we do this in an extra loop to make sure we don't create output
// objects twice
for (int i = 0; i < macroOutput.length; i++) {
initSpecialReferences(macroOutput[i], getOutput(i));
}
}
/**
* Adds a (macroGeo, algoGeo) pair to the map.
*/
private void map(GeoElementND macroGeo, GeoElement algoGeo) {
if (macroToAlgoMap.get(macroGeo) == null) {
// map macroGeo to algoGeo
macroToAlgoMap.put(macroGeo, algoGeo);
if (!isMacroInputObject(macroGeo)) {
macroOutputAndReferencedGeos.add(macroGeo);
// for efficiency: to avoid lookups in macroToAlgoMap
algoOutputAndReferencedGeos.add(algoGeo);
}
}
}
/**
* Returns a GeoElement in this algo's construction that corresponds to the
* given macroGeo from the macro construction. If a macro-geo is not yet
* mapped to an algo-geo, a new algo-geo is created and added to the map
* automatically.
*/
private GeoElement getAlgoGeo(GeoElementND macroGeo) {
if (macroGeo == null) {
return null;
}
GeoElement algoGeo = macroToAlgoMap.get(macroGeo);
// if we don't have a corresponding GeoElement in our map yet,
// create a new geo and update the map
if (algoGeo == null) {
algoGeo = createAlgoCopy(macroGeo);
map(macroGeo, algoGeo);
}
return algoGeo;
}
/**
* Creates a new algo-geo in this construction that is copy of macroGeo from
* the macro construction.
*/
private GeoElement createAlgoCopy(GeoElementND macroGeo) {
GeoElement algoGeo = macroGeo.copyInternal(cons);
return algoGeo;
}
/**
* Some GeoElement types need special settings as they reference other
* GeoElement objects. We need to make sure that algoGeo only reference
* objects in its own construction.
*/
private void initSpecialReferences(GeoElement macroGeo,
GeoElement algoGeo) {
switch (macroGeo.getGeoClassType()) {
case INTERVAL:
case FUNCTION:
initFunction(((GeoFunction) algoGeo).getFunction());
break;
case LIST:
initList((GeoList) macroGeo, (GeoList) algoGeo);
break;
case LINE:
initLine((GeoLine) macroGeo, (GeoLine) algoGeo);
break;
case POLYGON:
initPolygon((GeoPolygon) macroGeo, (GeoPolygon) algoGeo);
break;
case CONIC:
initConic((GeoConic) macroGeo, (GeoConic) algoGeo);
break;
case TEXT:
case VECTOR:
case IMAGE:
initLocateable((Locateable) macroGeo, (Locateable) algoGeo);
break;
default:
// no special treatment necessary at the moment
// case ANGLE:
// case BOOLEAN:
// case CONICPART:
// case LOCUS:
// case NUMERIC:
// case POINT:
// case AXIS:
// case RAY:
// case SEGMENT:
// case POLYGON:
}
}
/**
* Makes sure that the start and end point of a line are in its construction
* (if the line has this kind of information).
*/
private void initLine(GeoLine macroLine, GeoLine line) {
GeoPoint startPoint = (GeoPoint) getAlgoGeo(macroLine.getStartPoint());
GeoPoint endPoint = (GeoPoint) getAlgoGeo(macroLine.getEndPoint());
line.setStartPoint(startPoint);
line.setEndPoint(endPoint);
}
/**
* Makes sure that all points on conic are in its construction.
*/
private void initConic(GeoConic macroConic, GeoConic conic) {
ArrayList<GeoPointND> macroPoints = macroConic.getPointsOnConic();
if (macroPoints == null) {
return;
}
int size = macroPoints.size();
ArrayList<GeoPointND> points = new ArrayList<GeoPointND>(size);
for (int i = 0; i < size; i++) {
points.add((GeoPointND) getAlgoGeo(macroPoints.get(i)));
}
conic.setPointsOnConic(points);
}
/**
* Makes sure that the start points of locateable are in its construction.
*/
private void initLocateable(Locateable macroLocateable,
Locateable locateable) {
GeoPointND[] macroStartPoints = macroLocateable.getStartPoints();
if (macroStartPoints == null) {
return;
}
try {
for (int i = 0; i < macroStartPoints.length; i++) {
GeoPointND point = (GeoPointND) getAlgoGeo(macroStartPoints[i]);
locateable.initStartPoint(point, i);
// Application.debug("set start point: " + locateable + " => " +
// point + "(" + point.cons +")");
}
} catch (Exception e) {
Log.debug("AlgoMacro.initLocateable:");
e.printStackTrace();
}
}
/**
* Makes sure that the points and segments of poly are in its construction.
*/
private void initPolygon(GeoPolygon macroPoly, GeoPolygon poly) {
// points
GeoPointND[] macroPolyPoints = macroPoly.getPoints();
GeoPoint[] polyPoints = new GeoPoint[macroPolyPoints.length];
for (int i = 0; i < macroPolyPoints.length; i++) {
polyPoints[i] = (GeoPoint) getAlgoGeo(macroPolyPoints[i]);
}
poly.setPoints(polyPoints);
// // segments
// GeoSegment [] macroPolySegments = macroPoly.getSegments();
// GeoSegment [] polySegments = new
// GeoSegment[macroPolySegments.length];
// for (int i=0; i < macroPolySegments.length; i++) {
// polySegments[i] = (GeoSegment) getAlgoGeo( macroPolySegments[i] );
// initLine(macroPolySegments[i], polySegments[i]);
// }
// poly.setSegments(polySegments);
}
/**
* Makes sure that all referenced GeoElements of geoList are in its
* construction.
*
* @param macroList
* GeoList of macro geos
* @param geoList
* GeoList of construction geos
*/
@Override
final public void initList(GeoList macroList, GeoList geoList) {
// make sure all referenced GeoElements are from the algo-construction
int size = macroList.size();
geoList.clear();
geoList.ensureCapacity(size);
for (int i = 0; i < size; i++) {
geoList.add(getAlgoGeo(macroList.get(i)));
}
}
/**
* Makes sure that all referenced GeoElements of fun are in this algorithm's
* construction.
*
* @param fun
* function
*/
@Override
final public void initFunction(FunctionNVar fun) {
// geoFun was created as a copy of macroFun,
// make sure all referenced GeoElements are from the algo-construction
replaceReferencedMacroObjects(fun.getExpression());
}
/**
* Replaces all references to macroGeos in expression exp by references to
* the corresponding algoGeos
*/
private void replaceReferencedMacroObjects(ExpressionNode exp) {
ExpressionValue left = exp.getLeft();
ExpressionValue right = exp.getRight();
// left tree
if (left.isGeoElement()) {
GeoElement referencedGeo = (GeoElement) left;
if (macro.isInMacroConstruction(referencedGeo)) {
exp.setLeft(getAlgoGeo(referencedGeo));
}
} else if (left.isExpressionNode()) {
replaceReferencedMacroObjects((ExpressionNode) left);
}
// right tree
if (right == null) {
return;
} else if (right.isGeoElement()) {
GeoElement referencedGeo = (GeoElement) right;
if (macro.isInMacroConstruction(referencedGeo)) {
exp.setRight(getAlgoGeo(referencedGeo));
}
} else if (right.isExpressionNode()) {
replaceReferencedMacroObjects((ExpressionNode) right);
}
}
@Override
public boolean drawBefore(GeoElement geoElement, GeoElement other) {
int myIndex = 0, otherIndex = 0;
for (int i = 0; i < this.getOutputLength(); i++) {
if (this.algoOutputAndReferencedGeos.get(i) == geoElement) {
myIndex = this.macroOutputAndReferencedGeos.get(i)
.getConstructionIndex();
}
if (this.algoOutputAndReferencedGeos.get(i) == other) {
otherIndex = this.macroOutputAndReferencedGeos.get(i)
.getConstructionIndex();
}
}
return myIndex < otherIndex;
}
@Override
public boolean isChangeable(GeoElement out) {
for (int i = 0; i < macroOutput.length; i++) {
if (getOutput(i) == out && macroOutput[i].isPointerChangeable()) {
return true;
}
}
return false;
}
/**
* @param geoPoint
* parent construction point to be moved
* @param x
* desired homogeneous x-coord
* @param y
* desired homogeneous y-coord
* @param z
* desired homogeneous z-coord
*/
public void setCoords(GeoPoint geoPoint, double x, double y, double z) {
if (!isChangeable(geoPoint)) {
return;
}
if (this.locked) {
geoPoint.setCoords2D(x, y, z);
geoPoint.updateCoords();
return;
}
setMacroConstructionState();
// update all algorithms of macro-construction
macro.getMacroConstruction().updateAllAlgorithms();
// set algo geos to macro geos state
//
for (Entry<GeoElementND, GeoElement> entry : macroToAlgoMap
.entrySet()) {
GeoElementND me = entry.getKey();
if (entry.getValue() == geoPoint) {
GeoPoint mp = ((GeoPoint) me);
mp.setCoords(x, y, z);
mp.updateCascade();
geoPoint.setCoords2D(mp.getX(), mp.getY(), mp.getZ());
geoPoint.updateCoords();
}
}
macro.getMacroConstruction().updateAllAlgorithms();
getMacroConstructionState();
ArrayList<GeoElement> outputList = new ArrayList<GeoElement>(
getOutputLength());
for (int i = 0; i < getOutputLength(); i++) {
outputList.add(getOutput(i));
}
GeoElement.updateCascade(outputList, new TreeSet<AlgoElement>(), true);
kernel.notifyRepaint();
}
@Override
public int getRelatedModeID() {
return kernel.getMacroID(macro)
+ EuclidianConstants.MACRO_MODE_ID_OFFSET;
}
}