diff --git a/puzzles files/skyscrapers/1646651 b/puzzles files/skyscrapers/1646651
deleted file mode 100644
index 847d8639c..000000000
--- a/puzzles files/skyscrapers/1646651
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/puzzles files/skyscrapers/easy1.xml b/puzzles files/skyscrapers/easy1.xml
deleted file mode 100644
index 9d3135bff..000000000
--- a/puzzles files/skyscrapers/easy1.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/main/java/edu/rpi/legup/history/AutoCaseRuleCommand.java b/src/main/java/edu/rpi/legup/history/AutoCaseRuleCommand.java
index 331a3dddf..a4c157c77 100644
--- a/src/main/java/edu/rpi/legup/history/AutoCaseRuleCommand.java
+++ b/src/main/java/edu/rpi/legup/history/AutoCaseRuleCommand.java
@@ -62,6 +62,7 @@ public void executeCommand() {
board.setModifiable(false);
transition.setBoard(board);
transition.setRule(caseRule);
+ transition.setSelection(elementView.getPuzzleElement().copy());
caseTrans.add(transition);
TreeNode childNode = (TreeNode) tree.addTreeElement(transition);
diff --git a/src/main/java/edu/rpi/legup/model/gameboard/PuzzleElement.java b/src/main/java/edu/rpi/legup/model/gameboard/PuzzleElement.java
index 97c9205cf..3d84287e3 100644
--- a/src/main/java/edu/rpi/legup/model/gameboard/PuzzleElement.java
+++ b/src/main/java/edu/rpi/legup/model/gameboard/PuzzleElement.java
@@ -11,6 +11,7 @@ public abstract class PuzzleElement {
protected boolean isModified;
protected boolean isGiven;
protected boolean isValid;
+ protected int casesDepended;
/**
* PuzzleElement Constructor creates a new puzzle element.
@@ -22,6 +23,7 @@ public PuzzleElement() {
this.isModified = false;
this.isGiven = false;
this.isValid = true;
+ this.casesDepended = 0;
}
/**
@@ -148,6 +150,24 @@ public void setValid(boolean isValid) {
this.isValid = isValid;
}
+ /**
+ * Get the number of case rules that depend upon the state of this element
+ *
+ * @return number of cases
+ */
+ public int getCasesDepended() {
+ return this.casesDepended;
+ }
+
+ /**
+ * Sets the number of case rules that depend upon the state of this element
+ *
+ * @param cases number of cases
+ */
+ public void setCasesDepended(int cases) {
+ this.casesDepended = cases;
+ }
+
/**
* Tests whether two puzzle elements objects have the same puzzle element
*
diff --git a/src/main/java/edu/rpi/legup/model/rules/CaseRule.java b/src/main/java/edu/rpi/legup/model/rules/CaseRule.java
index d9c7e73e5..efb96e21a 100644
--- a/src/main/java/edu/rpi/legup/model/rules/CaseRule.java
+++ b/src/main/java/edu/rpi/legup/model/rules/CaseRule.java
@@ -7,9 +7,8 @@
import edu.rpi.legup.model.tree.TreeTransition;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
+import java.util.Set;
import static edu.rpi.legup.model.rules.RuleType.CASE;
@@ -79,6 +78,7 @@ public String checkRule(TreeTransition transition) {
String check = checkRuleRaw(transition);
+ // Mark transition and new data as valid or not
boolean isCorrect = (check == null);
for (TreeTransition childTrans : parentNodes.get(0).getChildren()) {
childTrans.setCorrect(isCorrect);
@@ -125,6 +125,31 @@ public String checkRuleAt(TreeTransition transition, PuzzleElement puzzleElement
*/
@Override
public abstract String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement);
+
+ /**
+ * Returns the elements necessary for the cases returned by getCases(board,puzzleElement) to be valid
+ * Overridden by case rules dependent on more than just the modified data
+ *
+ * @param board board state at application
+ * @param puzzleElement selected puzzleElement
+ * @return List of puzzle elements (typically cells) this application of the case rule depends upon.
+ * Defaults to any element modified by any case
+ */
+ public List dependentElements(Board board, PuzzleElement puzzleElement) {
+ List elements = new ArrayList<>();
+
+ List cases = getCases(board,puzzleElement);
+ for (Board caseBoard : cases) {
+ Set data = caseBoard.getModifiedData();
+ for (PuzzleElement element : data) {
+ if(!elements.contains(board.getPuzzleElement(element))){
+ elements.add(board.getPuzzleElement(element));
+ }
+ }
+ }
+
+ return elements;
+ }
}
diff --git a/src/main/java/edu/rpi/legup/model/rules/DirectRule.java b/src/main/java/edu/rpi/legup/model/rules/DirectRule.java
index 0465ca88c..847764b7b 100644
--- a/src/main/java/edu/rpi/legup/model/rules/DirectRule.java
+++ b/src/main/java/edu/rpi/legup/model/rules/DirectRule.java
@@ -33,6 +33,10 @@ public String checkRule(TreeTransition transition) {
transition.getParents().get(0).getChildren().size() != 1) {
return "State must have only 1 parent and 1 child";
}
+ else if (finalBoard.getModifiedData().isEmpty()) {
+ // null transition
+ return null;
+ }
else {
return checkRuleRaw(transition);
}
diff --git a/src/main/java/edu/rpi/legup/model/tree/TreeTransition.java b/src/main/java/edu/rpi/legup/model/tree/TreeTransition.java
index e1d042626..72572ac72 100644
--- a/src/main/java/edu/rpi/legup/model/tree/TreeTransition.java
+++ b/src/main/java/edu/rpi/legup/model/tree/TreeTransition.java
@@ -2,6 +2,7 @@
import edu.rpi.legup.model.gameboard.Board;
import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.CaseRule;
import edu.rpi.legup.model.rules.Rule;
import edu.rpi.legup.model.rules.RuleType;
@@ -12,6 +13,8 @@ public class TreeTransition extends TreeElement {
private ArrayList parents;
private TreeNode childNode;
private Rule rule;
+
+ private PuzzleElement selection;
private boolean isCorrect;
private boolean isVerified;
@@ -26,6 +29,7 @@ public TreeTransition(Board board) {
this.childNode = null;
this.board = board;
this.rule = null;
+ this.selection = null;
this.isCorrect = false;
this.isVerified = false;
}
@@ -87,13 +91,42 @@ public void propagateChange(PuzzleElement element) {
}
}
else {
+ // Overwrite previous modifications to this element
+ board.removeModifiedData(board.getPuzzleElement(element));
+
+ // apply changes to tranistion
+ board.notifyChange(element);
+
+ // mark first transition as modified
+ if (!board.getPuzzleElement(element).equalsData(parents.get(0).getBoard().getPuzzleElement(element))) {
+ board.addModifiedData(element);
+ }
+
+ // propagate to children
if (childNode != null) {
- board.notifyChange(element);
- childNode.getBoard().notifyChange(element.copy());
- for (TreeTransition child : childNode.getChildren()) {
- PuzzleElement copy = element.copy();
+
+ // find starting board
+ TreeNode head = childNode;
+ while (head.getParent() != null) {
+ head = head.getParent().getParents().get(0);
+ }
+ Board headBoard = head.getBoard();
+
+ PuzzleElement copy = element.copy();
+ // Set as modifiable if reverted to starting value (and started modifiable)
+ if (headBoard.getPuzzleElement(element).equalsData(element)) {
+ copy.setModifiable(headBoard.getPuzzleElement(element).isModifiable());
+ }
+ else{
copy.setModifiable(false);
- child.propagateChange(copy);
+ }
+
+ // apply changes to result node
+ childNode.getBoard().notifyChange(copy);
+
+ // apply to all child transitions
+ for (TreeTransition child : childNode.getChildren()) {
+ child.propagateChange(copy.copy());
}
}
}
@@ -327,6 +360,27 @@ public void setRule(Rule rule) {
isVerified = false;
}
+ /**
+ * Gets he selected element associated with this transition
+ *
+ * @return If this is a case rule, the selected element for that rule, null otherwise
+ */
+ public PuzzleElement getSelection() {
+ if (this.rule instanceof CaseRule) {
+ return selection;
+ }
+ return null;
+ }
+
+ /**
+ * Sets the selected element associated with this transition
+ *
+ * @param selection selected element for this transition
+ */
+ public void setSelection(PuzzleElement selection) {
+ this.selection = selection;
+ }
+
/**
* Gets whether this transition is correctly justified
*
diff --git a/src/main/java/edu/rpi/legup/puzzle/lightup/rules/SatisfyNumberCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/lightup/rules/SatisfyNumberCaseRule.java
index 1f166685b..cf7b70ccd 100644
--- a/src/main/java/edu/rpi/legup/puzzle/lightup/rules/SatisfyNumberCaseRule.java
+++ b/src/main/java/edu/rpi/legup/puzzle/lightup/rules/SatisfyNumberCaseRule.java
@@ -287,4 +287,67 @@ private List getAdjacentCells(LightUpBoard board, LightUpCell cell)
}
return cells;
}
+
+ /**
+ * Returns the elements necessary for the cases returned by getCases(board,puzzleElement) to be valid
+ * Overridden by case rules dependent on more than just the modified data
+ *
+ * @param board board state at application
+ * @param puzzleElement selected puzzleElement
+ * @return List of puzzle elements (typically cells) this application of the case rule depends upon.
+ * Defaults to any element modified by any case
+ */
+ @Override
+ public List dependentElements(Board board, PuzzleElement puzzleElement) {
+ List elements = new ArrayList<>();
+
+ LightUpBoard puzzleBoard = (LightUpBoard) board;
+ LightUpCell point = (LightUpCell)puzzleBoard.getPuzzleElement(puzzleElement);
+
+ List cells = getAdjacentCells(puzzleBoard,point);
+
+ for (LightUpCell cell : cells) {
+ //add cells that can light adjacents from any direction
+ Point location = cell.getLocation();
+ for (int i = location.x; i < puzzleBoard.getWidth(); i++) {
+ System.out.println(i);
+ LightUpCell c = puzzleBoard.getCell(i, location.y);
+ if (c.getType() == LightUpCellType.BLACK || c.getType() == LightUpCellType.NUMBER) {
+ break;
+ }
+ else if (!elements.contains(board.getPuzzleElement(c))) {
+ elements.add(board.getPuzzleElement(c));
+ }
+ }
+ for (int i = location.x; i >= 0; i--) {
+ LightUpCell c = puzzleBoard.getCell(i, location.y);
+ if (c.getType() == LightUpCellType.BLACK || c.getType() == LightUpCellType.NUMBER) {
+ break;
+ }
+ else if (!elements.contains(board.getPuzzleElement(c))) {
+ elements.add(board.getPuzzleElement(c));
+ }
+ }
+ for (int i = location.y; i < puzzleBoard.getHeight(); i++) {
+ LightUpCell c = puzzleBoard.getCell(location.x, i);
+ if (c.getType() == LightUpCellType.BLACK || c.getType() == LightUpCellType.NUMBER) {
+ break;
+ }
+ else if (!elements.contains(board.getPuzzleElement(c))) {
+ elements.add(board.getPuzzleElement(c));
+ }
+ }
+ for (int i = location.y; i >= 0; i--) {
+ LightUpCell c = puzzleBoard.getCell(location.x, i);
+ if (c.getType() == LightUpCellType.BLACK || c.getType() == LightUpCellType.NUMBER) {
+ break;
+ }
+ else if (!elements.contains(board.getPuzzleElement(c))) {
+ elements.add(board.getPuzzleElement(c));
+ }
+ }
+ }
+
+ return elements;
+ }
}
diff --git a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/rules/caserule/CaseRule_GenericStatement.java b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/rules/caserule/CaseRule_GenericStatement.java
index 2a40bf45d..0e25586a8 100644
--- a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/rules/caserule/CaseRule_GenericStatement.java
+++ b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/rules/caserule/CaseRule_GenericStatement.java
@@ -1,119 +1,137 @@
-package edu.rpi.legup.puzzle.shorttruthtable.rules.caserule;
-
-import edu.rpi.legup.model.gameboard.Board;
-import edu.rpi.legup.model.gameboard.CaseBoard;
-import edu.rpi.legup.model.gameboard.PuzzleElement;
-
-import edu.rpi.legup.puzzle.shorttruthtable.ShortTruthTableBoard;
-import edu.rpi.legup.puzzle.shorttruthtable.ShortTruthTableCell;
-import edu.rpi.legup.puzzle.shorttruthtable.ShortTruthTableCellType;
-import edu.rpi.legup.puzzle.shorttruthtable.ShortTruthTableStatement;
-import edu.rpi.legup.puzzle.shorttruthtable.ShortTruthTableOperation;
-
-
-import java.util.ArrayList;
-
-public abstract class CaseRule_GenericStatement extends CaseRule_Generic {
-
- public CaseRule_GenericStatement(String ruleID, char operation, String title,
- ShortTruthTableCellType[][] trueCases,
- ShortTruthTableCellType[][] falseCases) {
- super(ruleID, ShortTruthTableOperation.getRuleName(operation),
- title + " case",
- "A known " + title.toUpperCase() + " statement can have multiple forms");
-
- this.operation = operation;
-
- this.trueCases = trueCases;
- this.falseCases = falseCases;
- }
-
- private final char operation;
-
- private final ShortTruthTableCellType[][] trueCases;
- private final ShortTruthTableCellType[][] falseCases;
-
- protected static final ShortTruthTableCellType T = ShortTruthTableCellType.TRUE;
- protected static final ShortTruthTableCellType F = ShortTruthTableCellType.FALSE;
- protected static final ShortTruthTableCellType U = ShortTruthTableCellType.UNKNOWN;
-
- // Adds all elements that can be selected for this caserule
- @Override
- public CaseBoard getCaseBoard(Board board) {
- //copy the board and add all elements that can be selected
- ShortTruthTableBoard sttBoard = (ShortTruthTableBoard) board.copy();
- sttBoard.setModifiable(false);
- CaseBoard caseBoard = new CaseBoard(sttBoard, this);
-
- //add all elements that can be selected for the case rule statement
- for (PuzzleElement element : sttBoard.getPuzzleElements()) {
- //get the cell object
- ShortTruthTableCell cell = sttBoard.getCellFromElement(element);
- //the cell must match the symbol
- if (cell.getSymbol() != this.operation) continue;
- //the statement must be assigned with unassigned sub-statements
- if (!cell.getType().isTrueOrFalse()) continue;
- if (cell.getStatementReference().getRightStatement().getCell().getType().isTrueOrFalse()) continue;
- if (this.operation != ShortTruthTableOperation.NOT &&
- cell.getStatementReference().getRightStatement().getCell().getType().isTrueOrFalse()) {
- continue;
- }
- //if the element has passed all the checks, it can be selected
- caseBoard.addPickableElement(element);
- }
- return caseBoard;
- }
-
- /**
- * Gets the possible cases at a specific location based on this case rule
- *
- * @param board the current board state
- * @param puzzleElement equivalent puzzleElement
- * @return a list of elements the specified could be
- */
- @SuppressWarnings("unchecked")
- @Override
- public ArrayList getCases(Board board, PuzzleElement puzzleElement) {
- ShortTruthTableBoard sttBoard = ((ShortTruthTableBoard) board);
- ShortTruthTableCell cell = sttBoard.getCellFromElement(puzzleElement);
-
- // If the statement is set to true, collect true cases. Otherwise, collect the false cases
- if (cell.getType() == ShortTruthTableCellType.TRUE) {
- return getCasesFromCell(sttBoard, puzzleElement, trueCases);
- }
- return getCasesFromCell(sttBoard, puzzleElement, falseCases);
- }
-
- /**
- * Collects a list of boards for each possible outcome of case-rule application
- * @param board current board state
- * @param puzzleElement case rule operator
- * @param possibilities list of possibilities for operator state
- * @return ArrayList of Boards
- */
- private ArrayList getCasesFromCell(ShortTruthTableBoard board, PuzzleElement puzzleElement, ShortTruthTableCellType[][] possibilities) {
- // Create branch case for each possibility
- ArrayList cases = new ArrayList<>();
- for (int i = 0; i < possibilities.length; i++) {
- // Create a new board to modify and get statement of selected square
- ShortTruthTableBoard b = board.copy();
- ShortTruthTableCell cell = b.getCellFromElement(puzzleElement);
- ShortTruthTableStatement statement = cell.getStatementReference();
-
- // Modify neighboring cells of case-rule application by the provided logical cases
- if (possibilities[i][0] != ShortTruthTableCellType.UNKNOWN) {
- ShortTruthTableCell leftCell = statement.getLeftStatement().getCell();
- leftCell.setData(possibilities[i][0]);
- b.addModifiedData(leftCell);
- }
- if (possibilities[i][1] != ShortTruthTableCellType.UNKNOWN) {
- ShortTruthTableCell rightCell = statement.getRightStatement().getCell();
- rightCell.setData(possibilities[i][1]);
- b.addModifiedData(rightCell);
- }
-
- cases.add(b);
- }
- return cases;
- }
-}
+package edu.rpi.legup.puzzle.shorttruthtable.rules.caserule;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.CaseBoard;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+
+import edu.rpi.legup.puzzle.shorttruthtable.ShortTruthTableBoard;
+import edu.rpi.legup.puzzle.shorttruthtable.ShortTruthTableCell;
+import edu.rpi.legup.puzzle.shorttruthtable.ShortTruthTableCellType;
+import edu.rpi.legup.puzzle.shorttruthtable.ShortTruthTableStatement;
+import edu.rpi.legup.puzzle.shorttruthtable.ShortTruthTableOperation;
+
+import java.util.List;
+import java.util.ArrayList;
+
+public abstract class CaseRule_GenericStatement extends CaseRule_Generic {
+
+ public CaseRule_GenericStatement(String ruleID, char operation, String title,
+ ShortTruthTableCellType[][] trueCases,
+ ShortTruthTableCellType[][] falseCases) {
+ super(ruleID, ShortTruthTableOperation.getRuleName(operation),
+ title + " case",
+ "A known " + title.toUpperCase() + " statement can have multiple forms");
+
+ this.operation = operation;
+
+ this.trueCases = trueCases;
+ this.falseCases = falseCases;
+ }
+
+ private final char operation;
+
+ private final ShortTruthTableCellType[][] trueCases;
+ private final ShortTruthTableCellType[][] falseCases;
+
+ protected static final ShortTruthTableCellType T = ShortTruthTableCellType.TRUE;
+ protected static final ShortTruthTableCellType F = ShortTruthTableCellType.FALSE;
+ protected static final ShortTruthTableCellType U = ShortTruthTableCellType.UNKNOWN;
+
+ // Adds all elements that can be selected for this caserule
+ @Override
+ public CaseBoard getCaseBoard(Board board) {
+ //copy the board and add all elements that can be selected
+ ShortTruthTableBoard sttBoard = (ShortTruthTableBoard) board.copy();
+ sttBoard.setModifiable(false);
+ CaseBoard caseBoard = new CaseBoard(sttBoard, this);
+
+ //add all elements that can be selected for the case rule statement
+ for (PuzzleElement element : sttBoard.getPuzzleElements()) {
+ //get the cell object
+ ShortTruthTableCell cell = sttBoard.getCellFromElement(element);
+ //the cell must match the symbol
+ if (cell.getSymbol() != this.operation) continue;
+ //the statement must be assigned with unassigned sub-statements
+ if (!cell.getType().isTrueOrFalse()) continue;
+ if (cell.getStatementReference().getRightStatement().getCell().getType().isTrueOrFalse()) continue;
+ if (this.operation != ShortTruthTableOperation.NOT &&
+ cell.getStatementReference().getRightStatement().getCell().getType().isTrueOrFalse()) {
+ continue;
+ }
+ //if the element has passed all the checks, it can be selected
+ caseBoard.addPickableElement(element);
+ }
+ return caseBoard;
+ }
+
+ /**
+ * Gets the possible cases at a specific location based on this case rule
+ *
+ * @param board the current board state
+ * @param puzzleElement equivalent puzzleElement
+ * @return a list of elements the specified could be
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public ArrayList getCases(Board board, PuzzleElement puzzleElement) {
+ ShortTruthTableBoard sttBoard = ((ShortTruthTableBoard) board);
+ ShortTruthTableCell cell = sttBoard.getCellFromElement(puzzleElement);
+
+ // If the statement is set to true, collect true cases. Otherwise, collect the false cases
+ if (cell.getType() == ShortTruthTableCellType.TRUE) {
+ return getCasesFromCell(sttBoard, puzzleElement, trueCases);
+ }
+ return getCasesFromCell(sttBoard, puzzleElement, falseCases);
+ }
+
+ /**
+ * Collects a list of boards for each possible outcome of case-rule application
+ * @param board current board state
+ * @param puzzleElement case rule operator
+ * @param possibilities list of possibilities for operator state
+ * @return ArrayList of Boards
+ */
+ private ArrayList getCasesFromCell(ShortTruthTableBoard board, PuzzleElement puzzleElement, ShortTruthTableCellType[][] possibilities) {
+ // Create branch case for each possibility
+ ArrayList cases = new ArrayList<>();
+ for (int i = 0; i < possibilities.length; i++) {
+ // Create a new board to modify and get statement of selected square
+ ShortTruthTableBoard b = board.copy();
+ ShortTruthTableCell cell = b.getCellFromElement(puzzleElement);
+ ShortTruthTableStatement statement = cell.getStatementReference();
+
+ // Modify neighboring cells of case-rule application by the provided logical cases
+ if (possibilities[i][0] != ShortTruthTableCellType.UNKNOWN) {
+ ShortTruthTableCell leftCell = statement.getLeftStatement().getCell();
+ leftCell.setData(possibilities[i][0]);
+ b.addModifiedData(leftCell);
+ }
+ if (possibilities[i][1] != ShortTruthTableCellType.UNKNOWN) {
+ ShortTruthTableCell rightCell = statement.getRightStatement().getCell();
+ rightCell.setData(possibilities[i][1]);
+ b.addModifiedData(rightCell);
+ }
+
+ cases.add(b);
+ }
+ return cases;
+ }
+
+ /**
+ * Returns the elements necessary for the cases returned by getCases(board,puzzleElement) to be valid
+ * Overridden by case rules dependent on more than just the modified data
+ *
+ * @param board board state at application
+ * @param puzzleElement selected puzzleElement
+ * @return List of puzzle elements (typically cells) this application of the case rule depends upon.
+ * Defaults to any element modified by any case
+ */
+ @Override
+ public List dependentElements(Board board, PuzzleElement puzzleElement) {
+ List elements = super.dependentElements(board,puzzleElement);
+
+ elements.add(board.getPuzzleElement(puzzleElement));
+
+ return elements;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersBoard.java b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersBoard.java
index ca6bcbe02..52e8a6400 100644
--- a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersBoard.java
+++ b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersBoard.java
@@ -25,9 +25,6 @@ public class SkyscrapersBoard extends GridBoard {
private boolean viewFlag = false;
private boolean dupeFlag = false;
- private SkyscrapersClue modClue = null;
- //helper variable for case rule verification, tracks recently modified row/col
-
public SkyscrapersBoard(int size) {
super(size, size);
@@ -94,14 +91,6 @@ public void setViewFlag(boolean newFlag) {
viewFlag = newFlag;
}
- public SkyscrapersClue getmodClue() {
- return modClue;
- }
-
- public void setModClue(SkyscrapersClue newClue) {
- modClue = newClue;
- }
-
@Override
public SkyscrapersCell getCell(int x, int y) {
return (SkyscrapersCell) super.getCell(x, y);
diff --git a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersClue.java b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersClue.java
index dc68f45c7..1e7b1b45e 100644
--- a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersClue.java
+++ b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersClue.java
@@ -47,6 +47,9 @@ public void setType(SkyscrapersType type) {
}
public SkyscrapersClue copy() {
- return null;
+ SkyscrapersClue copy = new SkyscrapersClue(data, clueIndex, type);
+ copy.setIndex(index);
+ copy.setModifiable(isModifiable);
+ return copy;
}
}
diff --git a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/rules/CellForNumberCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/rules/CellForNumberCaseRule.java
index 683b742bf..01527294a 100644
--- a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/rules/CellForNumberCaseRule.java
+++ b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/rules/CellForNumberCaseRule.java
@@ -69,7 +69,6 @@ public ArrayList getCasesFor(Board board, PuzzleElement puzzleElement, In
PuzzleElement newCell = newCase.getPuzzleElement(cell);
newCell.setData(number);
newCase.addModifiedData(newCell);
- newCase.setModClue((SkyscrapersClue) newCase.getPuzzleElement(clue));
//if flags
boolean passed = true;
@@ -103,12 +102,7 @@ public String checkRuleRaw(TreeTransition transition) {
return "This case rule must have at least one child.";
}
- //find changed row/col
- SkyscrapersClue modClue = ((SkyscrapersBoard) childTransitions.get(0).getBoard()).getmodClue();
-
- //System.out.println(modClue.getType());
- //System.out.println(modClue.getClueIndex());
- if (childTransitions.size() != getCasesFor(oldBoard, modClue, (Integer) childTransitions.get(0).getBoard().getModifiedData().iterator().next().getData()).size()) {
+ if (childTransitions.size() != getCasesFor(oldBoard, oldBoard.getPuzzleElement(transition.getSelection()), (Integer) childTransitions.get(0).getBoard().getModifiedData().iterator().next().getData()).size()) {
//System.out.println("Wrong number of cases.");
return "Wrong number of cases.";
}
@@ -132,4 +126,49 @@ public String checkRuleRaw(TreeTransition transition) {
public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
return checkRuleRaw(transition);
}
+
+ /**
+ * Returns the elements necessary for the cases returned by getCases(board,puzzleElement) to be valid
+ * Overridden by case rules dependent on more than just the modified data
+ *
+ * @param board board state at application
+ * @param puzzleElement selected puzzleElement
+ * @return List of puzzle elements (typically cells) this application of the case rule depends upon.
+ * Defaults to any element modified by any case
+ */
+ @Override
+ public List dependentElements(Board board, PuzzleElement puzzleElement) {
+ List elements = new ArrayList<>();
+
+ SkyscrapersBoard puzzleBoard = (SkyscrapersBoard) board;
+ SkyscrapersClue clue = (SkyscrapersClue)puzzleBoard.getPuzzleElement(puzzleElement);
+
+ // check each point in modified row/col
+ List data = puzzleBoard.getRowCol(clue.getClueIndex(),SkyscrapersType.ANY,clue.getType() == SkyscrapersType.CLUE_WEST);
+ for (SkyscrapersCell point : data) {
+ List cells = new ArrayList<>(List.of(point));
+
+ // if dependent on row/col
+ if ((puzzleBoard.getDupeFlag() || puzzleBoard.getViewFlag()) && point.getType() == SkyscrapersType.UNKNOWN) {
+ // get perpendicular row/col intersecting this point
+ int index;
+ if (clue.getType() == SkyscrapersType.CLUE_WEST) {
+ index = point.getLocation().x;
+ }
+ else {
+ index = point.getLocation().y;
+ }
+ cells.addAll(puzzleBoard.getRowCol(index,SkyscrapersType.ANY,clue.getType() != SkyscrapersType.CLUE_WEST));
+ }
+
+ // add all to result
+ for (SkyscrapersCell cell : cells) {
+ if (!elements.contains(board.getPuzzleElement(cell))) {
+ elements.add(board.getPuzzleElement(cell));
+ }
+ }
+ }
+
+ return elements;
+ }
}
diff --git a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/rules/NumberForCellCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/rules/NumberForCellCaseRule.java
index a061c62a3..3bf0de70a 100644
--- a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/rules/NumberForCellCaseRule.java
+++ b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/rules/NumberForCellCaseRule.java
@@ -7,6 +7,7 @@
import edu.rpi.legup.model.tree.TreeTransition;
import edu.rpi.legup.puzzle.skyscrapers.SkyscrapersBoard;
import edu.rpi.legup.puzzle.skyscrapers.SkyscrapersCell;
+import edu.rpi.legup.puzzle.skyscrapers.SkyscrapersClue;
import edu.rpi.legup.puzzle.skyscrapers.SkyscrapersType;
import java.awt.*;
@@ -134,4 +135,38 @@ public String checkRuleRaw(TreeTransition transition) {
public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
return checkRuleRaw(transition);
}
+
+ /**
+ * Returns the elements necessary for the cases returned by getCases(board,puzzleElement) to be valid
+ * Overridden by case rules dependent on more than just the modified data
+ *
+ * @param board board state at application
+ * @param puzzleElement selected puzzleElement
+ * @return List of puzzle elements (typically cells) this application of the case rule depends upon.
+ * Defaults to any element modified by any case
+ */
+ @Override
+ public List dependentElements(Board board, PuzzleElement puzzleElement) {
+ List elements = new ArrayList<>();
+
+ SkyscrapersBoard puzzleBoard = (SkyscrapersBoard) board;
+ SkyscrapersCell point = (SkyscrapersCell)puzzleBoard.getPuzzleElement(puzzleElement);
+
+ List cells = new ArrayList<>(List.of(point));
+
+ // if dependent on row/col
+ if (puzzleBoard.getDupeFlag() || puzzleBoard.getViewFlag()) {
+ // add all cells in row/col intersecting given point
+ cells.addAll(puzzleBoard.getRowCol(point.getLocation().x,SkyscrapersType.ANY,false));
+ cells.addAll(puzzleBoard.getRowCol(point.getLocation().y,SkyscrapersType.ANY,true));
+ }
+
+ for (SkyscrapersCell cell : cells) {
+ if (!elements.contains(board.getPuzzleElement(cell))) {
+ elements.add(board.getPuzzleElement(cell));
+ }
+ }
+
+ return elements;
+ }
}
diff --git a/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentBoard.java b/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentBoard.java
index 2542ea335..dc809f34d 100644
--- a/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentBoard.java
+++ b/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentBoard.java
@@ -124,21 +124,11 @@ public void notifyDeletion(PuzzleElement puzzleElement) {
public List getAdjacent(TreeTentCell cell, TreeTentType type) {
List adj = new ArrayList<>();
Point loc = cell.getLocation();
- TreeTentCell up = getCell(loc.x, loc.y - 1);
- TreeTentCell right = getCell(loc.x + 1, loc.y);
- TreeTentCell down = getCell(loc.x, loc.y + 1);
- TreeTentCell left = getCell(loc.x - 1, loc.y);
- if (up != null && up.getType() == type) {
- adj.add(up);
- }
- if (right != null && right.getType() == type) {
- adj.add(right);
- }
- if (down != null && down.getType() == type) {
- adj.add(down);
- }
- if (left != null && left.getType() == type) {
- adj.add(left);
+ for (int i = -2; i < 2; i++) {
+ TreeTentCell adjCell = getCell(loc.x + (i % 2), loc.y + ((i + 1) % 2));
+ if (adjCell != null && adjCell.getType() == type) {
+ adj.add(adjCell);
+ }
}
return adj;
}
diff --git a/src/main/java/edu/rpi/legup/puzzle/treetent/rules/FillinRowCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/treetent/rules/FillinRowCaseRule.java
index 538772b74..698b3aa5e 100644
--- a/src/main/java/edu/rpi/legup/puzzle/treetent/rules/FillinRowCaseRule.java
+++ b/src/main/java/edu/rpi/legup/puzzle/treetent/rules/FillinRowCaseRule.java
@@ -160,4 +160,29 @@ public String checkRuleRaw(TreeTransition transition) {
public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
return null;
}
+
+ /**
+ * Returns the elements necessary for the cases returned by getCases(board,puzzleElement) to be valid
+ * Overridden by case rules dependent on more than just the modified data
+ *
+ * @param board board state at application
+ * @param puzzleElement selected puzzleElement
+ * @return List of puzzle elements (typically cells) this application of the case rule depends upon.
+ * Defaults to any element modified by any case
+ */
+ @Override
+ public List dependentElements(Board board, PuzzleElement puzzleElement) {
+ List elements = new ArrayList<>();
+
+ TreeTentBoard treeTentBoard = (TreeTentBoard) board;
+ TreeTentClue clue = (TreeTentClue) puzzleElement;
+
+ // add all elements of filled row
+ for (int i = 0; i < treeTentBoard.getWidth(); i++) {
+ TreeTentCell cell = treeTentBoard.getCell(i, clue.getClueIndex()-1);
+ elements.add(board.getPuzzleElement((cell)));
+ }
+
+ return elements;
+ }
}
diff --git a/src/main/java/edu/rpi/legup/puzzle/treetent/rules/LinkTentCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/treetent/rules/LinkTentCaseRule.java
index 36f466f87..39b1d0251 100644
--- a/src/main/java/edu/rpi/legup/puzzle/treetent/rules/LinkTentCaseRule.java
+++ b/src/main/java/edu/rpi/legup/puzzle/treetent/rules/LinkTentCaseRule.java
@@ -5,7 +5,6 @@
import edu.rpi.legup.model.gameboard.PuzzleElement;
import edu.rpi.legup.model.rules.CaseRule;
import edu.rpi.legup.model.tree.TreeTransition;
-import edu.rpi.legup.puzzle.treetent.TreeTent;
import edu.rpi.legup.puzzle.treetent.TreeTentBoard;
import edu.rpi.legup.puzzle.treetent.TreeTentType;
import edu.rpi.legup.puzzle.treetent.TreeTentCell;
@@ -151,4 +150,18 @@ public String checkRuleRaw(TreeTransition transition) {
public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
return checkRuleRaw(transition);
}
+
+ /**
+ * Returns the elements necessary for the cases returned by getCases(board,puzzleElement) to be valid
+ * Overridden by case rules dependent on more than just the modified data
+ *
+ * @param board board state at application
+ * @param puzzleElement selected puzzleElement
+ * @return List of puzzle elements (typically cells) this application of the case rule depends upon.
+ * Defaults to any element modified by any case
+ */
+ @Override
+ public List dependentElements(Board board, PuzzleElement puzzleElement) {
+ return List.of(board.getPuzzleElement(puzzleElement));
+ }
}
diff --git a/src/main/java/edu/rpi/legup/puzzle/treetent/rules/LinkTreeCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/treetent/rules/LinkTreeCaseRule.java
index 249547301..72ffd62eb 100644
--- a/src/main/java/edu/rpi/legup/puzzle/treetent/rules/LinkTreeCaseRule.java
+++ b/src/main/java/edu/rpi/legup/puzzle/treetent/rules/LinkTreeCaseRule.java
@@ -10,6 +10,7 @@
import edu.rpi.legup.puzzle.treetent.TreeTentLine;
import edu.rpi.legup.puzzle.treetent.TreeTentType;
+import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -152,4 +153,32 @@ public String checkRuleRaw(TreeTransition transition) {
public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
return checkRuleRaw(transition);
}
+
+ /**
+ * Returns the elements necessary for the cases returned by getCases(board,puzzleElement) to be valid
+ * Overridden by case rules dependent on more than just the modified data
+ *
+ * @param board board state at application
+ * @param puzzleElement selected puzzleElement
+ * @return List of puzzle elements (typically cells) this application of the case rule depends upon.
+ * Defaults to any element modified by any case
+ */
+ @Override
+ public List dependentElements(Board board, PuzzleElement puzzleElement) {
+ List elements = new ArrayList<>(List.of(board.getPuzzleElement(puzzleElement)));
+
+ TreeTentBoard treeTentBoard = (TreeTentBoard) board;
+ TreeTentCell point = (TreeTentCell) puzzleElement;
+
+ // get all adjacent cells
+ Point loc = point.getLocation();
+ for (int i = -2; i < 2; i++) {
+ TreeTentCell cell = treeTentBoard.getCell(loc.x + (i % 2), loc.y + ((i + 1) % 2));
+ if (cell != null) {
+ elements.add(board.getPuzzleElement(cell));
+ }
+ }
+
+ return elements;
+ }
}
diff --git a/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeView.java b/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeView.java
index ecf59146d..9bfffe60a 100644
--- a/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeView.java
+++ b/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeView.java
@@ -1,756 +1,886 @@
-package edu.rpi.legup.ui.proofeditorui.treeview;
-
-import edu.rpi.legup.app.GameBoardFacade;
-import edu.rpi.legup.controller.TreeController;
-import edu.rpi.legup.model.observer.ITreeListener;
-import edu.rpi.legup.model.tree.Tree;
-import edu.rpi.legup.model.tree.TreeElement;
-import edu.rpi.legup.model.tree.TreeNode;
-import edu.rpi.legup.model.tree.TreeTransition;
-import edu.rpi.legup.ui.ScrollView;
-import edu.rpi.legup.utility.DisjointSets;
-
-import javax.swing.*;
-import java.awt.*;
-import java.awt.image.BufferedImage;
-import java.util.*;
-import java.util.List;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import static edu.rpi.legup.model.tree.TreeElementType.NODE;
-import static edu.rpi.legup.model.tree.TreeElementType.TRANSITION;
-import static edu.rpi.legup.ui.proofeditorui.treeview.TreeNodeView.DIAMETER;
-import static edu.rpi.legup.ui.proofeditorui.treeview.TreeNodeView.RADIUS;
-
-public class TreeView extends ScrollView implements ITreeListener {
- private final static Logger LOGGER = LogManager.getLogger(TreeView.class.getName());
-
- private static final int TRANS_GAP = 5;
-
- private static final int NODE_GAP_WIDTH = 70;
- private static final int NODE_GAP_HEIGHT = 15;
-
- private static final int BORDER_GAP_HEIGHT = 20;
- private static final int BORDER_GAP_WIDTH = 20;
-
- private static final int BORDER_SPACING = 100;
-
- private TreeNodeView nodeHover;
-
- private ArrayList currentStateBoxes;
- private Rectangle bounds = new Rectangle(0, 0, 0, 0);
-
- private Tree tree;
- private TreeNodeView rootNodeView;
- private Map viewMap;
- private Dimension dimension;
-
- private TreeViewSelection selection;
-
- public TreeView(TreeController treeController) {
- super(treeController);
- currentStateBoxes = new ArrayList<>();
- setSize(dimension = new Dimension(100, 200));
- setPreferredSize(new Dimension(640, 160));
-
- viewMap = new HashMap<>();
-
- selection = new TreeViewSelection();
- }
-
- public TreeViewSelection getSelection() {
- return selection;
- }
-
- /**
- * Gets the tree node puzzleElement that the mouse is hovering over
- *
- * @return tree node puzzleElement that the mouse is hovering over
- */
- public TreeNodeView getNodeHover() {
- return nodeHover;
- }
-
- /**
- * Sets the tree node puzzleElement that the mouse is hovering over
- *
- * @param nodeHover tree node puzzleElement the mouse is hovering over
- */
- public void setNodeHover(TreeNodeView nodeHover) {
- this.nodeHover = nodeHover;
- }
-
- /**
- * Gets the TreeElementView by the specified point or null if no view exists at the specified point
- *
- * @param point location to query for a view
- * @return TreeElementView at the point specified, otherwise null
- */
- public TreeElementView getTreeElementView(Point point) {
- return getTreeElementView(point, rootNodeView);
- }
-
- /**
- * Recursively gets the TreeElementView by the specified point or null if no view exists at the specified point or
- * the view specified is null
- *
- * @param point location to query for a view
- * @param elementView view to determine if the point is contained within it
- * @return TreeElementView at the point specified, otherwise null
- */
- private TreeElementView getTreeElementView(Point point, TreeElementView elementView) {
- if (elementView == null) {
- return null;
- }
- else {
- if (elementView.contains(point) && elementView.isVisible()) {
- if (elementView.getType() == NODE && ((TreeNodeView) elementView).isContradictoryState()) {
- return null;
- }
- return elementView;
- }
- else {
- if (elementView.getType() == NODE) {
- TreeNodeView nodeView = (TreeNodeView) elementView;
- for (TreeTransitionView transitionView : nodeView.getChildrenViews()) {
- TreeElementView view = getTreeElementView(point, transitionView);
- if (view != null) {
- return view;
- }
- }
- }
- else {
- TreeTransitionView transitionView = (TreeTransitionView) elementView;
- return getTreeElementView(point, transitionView.getChildView());
- }
- }
- }
- return null;
- }
-
- public void updateTreeView(Tree tree) {
- this.tree = tree;
- if (selection.getSelectedViews().size() == 0) {
- selection.newSelection(new TreeNodeView(tree.getRootNode()));
- }
- repaint();
- }
-
- /**
- * Sets the tree associated with this TreeView
- *
- * @param tree tree
- */
- public void setTree(Tree tree) {
- this.tree = tree;
- }
-
- public void updateTreeSize() {
- if (GameBoardFacade.getInstance().getTree() == null) {
- return;
- }
- setSize(bounds.getSize());
- }
-
- public void reset() {
- if (bounds.x != 0 || bounds.y != 0) {
- updateTreeSize();
- }
- }
-
- public void zoomFit() {
- double fitWidth = (viewport.getWidth() - 8.0) / (getSize().width - 200);
- double fitHeight = (viewport.getHeight() - 8.0) / (getSize().height - 120);
- zoomTo(Math.min(fitWidth, fitHeight));
- viewport.setViewPosition(new Point(0, viewport.getHeight() / 2));
- }
-
- /**
- * Creates a customized viewport for the scroll pane
- *
- * @return viewport for the scroll pane
- */
- @Override
- protected JViewport createViewport() {
- return new JViewport() {
- @Override
- protected LayoutManager createLayoutManager() {
- return new ViewportLayout() {
- @Override
- public void layoutContainer(Container parent) {
- Point point = viewport.getViewPosition();
- // determine the maximum x and y view positions
- int mx = getCanvas().getWidth() - viewport.getWidth();
- int my = getCanvas().getHeight() - viewport.getHeight();
- // obey edge boundaries
- if (point.x < 0) {
- point.x = 0;
- }
- if (point.x > mx) {
- point.x = mx;
- }
- if (point.y < 0) {
- point.y = 0;
- }
- if (point.y > my) {
- point.y = my;
- }
- // center margins
- if (mx < 0) {
- point.x = 0;
- }
- if (my < 0) {
- point.y = my / 2;
- }
- viewport.setViewPosition(point);
- }
- };
- }
- };
- }
-
- public void draw(Graphics2D graphics2D) {
- currentStateBoxes.clear();
- Tree tree = GameBoardFacade.getInstance().getTree();
- if (tree != null) {
- //setSize(bounds.getDimension());
- graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
- graphics2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
-
- drawTree(graphics2D);
-
- dimension.width += BORDER_SPACING;
- setSize(dimension);
-// graphics2D.drawRect(0,0, dimension.width, dimension.height);
-
- if (selection.getHover() != null) {
- drawMouseOver(graphics2D);
- }
- }
- }
-
- public void zoomReset() {
- zoomTo(1.0);
- viewport.setViewPosition(new Point(0, 0));
- }
-
- private void redrawTree(Graphics2D graphics2D, TreeNodeView nodeView) {
- if (nodeView != null) {
- nodeView.draw(graphics2D);
- for (TreeTransitionView transitionView : nodeView.getChildrenViews()) {
- transitionView.draw(graphics2D);
- redrawTree(graphics2D, transitionView.getChildView());
- }
- }
- }
-
- public void removeTreeElement(TreeElementView view) {
- if (view.getType() == NODE) {
- TreeNodeView nodeView = (TreeNodeView) view;
- nodeView.getParentView().setChildView(null);
- }
- else {
- TreeTransitionView transitionView = (TreeTransitionView) view;
- transitionView.getParentViews().forEach((TreeNodeView n) -> n.removeChildrenView(transitionView));
- }
- }
-
- /**
- * When the edu.rpi.legup.user hovers over the transition, draws the corresponding rules image
- *
- * @param g the graphics to use to draw
- */
- public void drawMouseOver(Graphics2D g) {
- if (selection.getHover().getType() == TRANSITION && ((TreeTransitionView) selection.getHover()).getTreeElement().isJustified()) {
- TreeTransition transition = (TreeTransition) selection.getHover().treeElement;
- int imgWidth = 100;
- int imgHeight = 100;
-
- BufferedImage image = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_ARGB);
- image.createGraphics().drawImage(transition.getRule().getImageIcon().getImage(), 0, 0, null);
- Point mousePoint = selection.getMousePoint();
- g.drawImage(image, mousePoint.x, mousePoint.y - 50, imgWidth, imgHeight, null);
- }
- }
-
- public void resetView() {
- this.tree = null;
- this.rootNodeView = null;
- this.selection.clearSelection();
- this.selection.clearHover();
- }
-
- /**
- * Called when a tree puzzleElement is added to the tree
- *
- * @param treeElement TreeElement that was added to the tree
- */
- @Override
- public void onTreeElementAdded(TreeElement treeElement) {
- if (treeElement.getType() == NODE) {
- addTreeNode((TreeNode) treeElement);
- }
- else {
- addTreeTransition((TreeTransition) treeElement);
- }
- repaint();
- }
-
- /**
- * Called when a tree puzzleElement is removed from the tree
- *
- * @param element TreeElement that was removed to the tree
- */
- @Override
- public void onTreeElementRemoved(TreeElement element) {
- if (element.getType() == NODE) {
- TreeNode node = (TreeNode) element;
- TreeNodeView nodeView = (TreeNodeView) viewMap.get(node);
-
- nodeView.getParentView().setChildView(null);
- removeTreeNode(node);
- }
- else {
- TreeTransition trans = (TreeTransition) element;
- TreeTransitionView transView = (TreeTransitionView) viewMap.get(trans);
-
- transView.getParentViews().forEach(n -> n.removeChildrenView(transView));
- removeTreeTransition(trans);
- }
- repaint();
- }
-
- /**
- * Called when the tree selection was changed
- *
- * @param selection tree selection that was changed
- */
- @Override
- public void onTreeSelectionChanged(TreeViewSelection selection) {
- this.selection.getSelectedViews().forEach(v -> v.setSelected(false));
- selection.getSelectedViews().forEach(v -> v.setSelected(true));
- this.selection = selection;
- repaint();
- }
-
- /**
- * Called when the model has finished updating the tree.
- */
- @Override
- public void onUpdateTree() {
- repaint();
- }
-
- /**
- * Gets the TreeElementView by the corresponding TreeElement associated with it
- *
- * @param element TreeElement of the view
- * @return TreeElementView of the TreeElement associated with it
- */
- public TreeElementView getElementView(TreeElement element) {
- return viewMap.get(element);
- }
-
- private void removeTreeNode(TreeNode node) {
- viewMap.remove(node);
- node.getChildren().forEach(t -> removeTreeTransition(t));
- }
-
- private void removeTreeTransition(TreeTransition trans) {
- viewMap.remove(trans);
- if (trans.getChildNode() != null) {
- removeTreeNode(trans.getChildNode());
- }
- }
-
- private void addTreeNode(TreeNode node) {
- TreeTransition parent = node.getParent();
-
- TreeNodeView nodeView = new TreeNodeView(node);
- TreeTransitionView parentView = (TreeTransitionView) viewMap.get(parent);
-
- nodeView.setParentView(parentView);
- parentView.setChildView(nodeView);
-
- viewMap.put(node, nodeView);
-
- if (!node.getChildren().isEmpty()) {
- node.getChildren().forEach(t -> addTreeTransition(t));
- }
- }
-
- private void addTreeTransition(TreeTransition trans) {
- List parents = trans.getParents();
-
- TreeTransitionView transView = new TreeTransitionView(trans);
- for (TreeNode parent : parents) {
- TreeNodeView parentNodeView = (TreeNodeView) viewMap.get(parent);
- transView.addParentView(parentNodeView);
- parentNodeView.addChildrenView(transView);
- }
-
- viewMap.put(trans, transView);
-
- if (trans.getChildNode() != null) {
- addTreeNode(trans.getChildNode());
- }
- }
-
- ///New Draw Methods
-
- public void drawTree(Graphics2D graphics2D) {
- if (tree == null) {
- LOGGER.error("Unable to draw tree.");
- }
- else {
- if (rootNodeView == null) {
- rootNodeView = new TreeNodeView(tree.getRootNode());
-
- LOGGER.debug("Creating new views for tree view.");
- createViews(rootNodeView);
-
- selection.newSelection(rootNodeView);
- }
-
- dimension = new Dimension(0, 0);
- calcSpan(rootNodeView);
- rootNodeView.setSpan(rootNodeView.getSpan() + DIAMETER + BORDER_SPACING);
-
- calculateViewLocations(rootNodeView, 0);
- dimension.height = (int) rootNodeView.getSpan();
-
- redrawTree(graphics2D, rootNodeView);
- LOGGER.debug("DrawTree: dimensions - " + dimension.width + "x" + dimension.height);
- }
- }
-
- public void createViews(TreeNodeView nodeView) {
- if (nodeView != null) {
- viewMap.put(nodeView.getTreeElement(), nodeView);
-
- TreeNode node = nodeView.getTreeElement();
- for (TreeTransition trans : node.getChildren()) {
- TreeTransitionView transView = (TreeTransitionView) viewMap.get(trans);
- if (transView != null) {
- nodeView.addChildrenView(transView);
- transView.addParentView(nodeView);
- break;
- }
- transView = new TreeTransitionView(trans);
-
- viewMap.put(transView.getTreeElement(), transView);
-
- transView.addParentView(nodeView);
- nodeView.addChildrenView(transView);
-
- TreeNode childNode = trans.getChildNode();
- if (childNode != null) {
- TreeNodeView childNodeView = new TreeNodeView(childNode);
- viewMap.put(childNodeView.getTreeElement(), childNodeView);
-
- childNodeView.setParentView(transView);
- transView.setChildView(childNodeView);
-
- createViews(childNodeView);
- }
- }
- }
- }
-
- public void calculateViewLocations(TreeNodeView nodeView, int depth) {
- nodeView.setDepth(depth);
- int xLoc = (NODE_GAP_WIDTH + DIAMETER) * depth + DIAMETER;
- nodeView.setX(xLoc);
- dimension.width = Math.max(dimension.width, xLoc);
-
- TreeTransitionView parentTransView = nodeView.getParentView();
- int yLoc = parentTransView == null ? (int) nodeView.getSpan() / 2 : parentTransView.getEndY();
- nodeView.setY(yLoc);
-
- ArrayList children = nodeView.getChildrenViews();
- switch (children.size()) {
- case 0:
- break;
- case 1: {
- TreeTransitionView childView = children.get(0);
-
- List parentsViews = childView.getParentViews();
- if (parentsViews.size() == 1) {
- childView.setEndY(yLoc);
-
- childView.setDepth(depth);
-
- Point lineStartPoint = childView.getLineStartPoint(0);
- lineStartPoint.x = xLoc + RADIUS + TRANS_GAP / 2;
- lineStartPoint.y = yLoc;
- childView.setEndX((NODE_GAP_WIDTH + DIAMETER) * (depth + 1) + RADIUS - TRANS_GAP / 2);
-
- dimension.width = Math.max(dimension.width, childView.getEndX());
-
- TreeNodeView childNodeView = childView.getChildView();
- if (childNodeView != null) {
- calculateViewLocations(childNodeView, depth + 1);
- }
- }
- else {
- if (parentsViews.size() > 1 && parentsViews.get(parentsViews.size() - 1) == nodeView) {
- int yAvg = 0;
- for (int i = 0; i < parentsViews.size(); i++) {
- TreeNodeView parentNodeView = parentsViews.get(i);
- depth = Math.max(depth, parentNodeView.getDepth());
- yAvg += parentNodeView.getY();
-
- Point lineStartPoint = childView.getLineStartPoint(i);
- lineStartPoint.x = parentNodeView.getX() + RADIUS + TRANS_GAP / 2;
- lineStartPoint.y = parentNodeView.getY();
- }
- yAvg /= parentsViews.size();
- childView.setEndY(yAvg);
-
- childView.setDepth(depth);
-
- childView.setEndX((NODE_GAP_WIDTH + DIAMETER) * (depth + 1) + RADIUS - TRANS_GAP / 2);
-
- dimension.width = Math.max(dimension.width, childView.getEndX());
-
- TreeNodeView childNodeView = childView.getChildView();
- if (childNodeView != null) {
- calculateViewLocations(childNodeView, depth + 1);
- }
- }
- }
- break;
- }
- default: {
- int span = 0;
- for (TreeTransitionView childView : children) {
- span += childView.getSpan();
- }
-
- span = (int) ((nodeView.getSpan() - span) / 2);
- for (int i = 0; i < children.size(); i++) {
- TreeTransitionView childView = children.get(i);
-
- childView.setDepth(depth);
-
- Point lineStartPoint = childView.getLineStartPoint(0);
- lineStartPoint.x = xLoc + RADIUS + TRANS_GAP / 2;
- lineStartPoint.y = yLoc;
- childView.setEndX((NODE_GAP_WIDTH + DIAMETER) * (depth + 1) + RADIUS - TRANS_GAP / 2);
- childView.setEndY(yLoc - (int) (nodeView.getSpan() / 2) + span + (int) (childView.getSpan() / 2));
-
- span += childView.getSpan();
- TreeNodeView childNodeView = childView.getChildView();
- if (childNodeView != null) {
- calculateViewLocations(childNodeView, depth + 1);
- }
- }
- break;
- }
- }
- }
-
- public void calcSpan(TreeElementView view) {
- if (view.getType() == NODE) {
- TreeNodeView nodeView = (TreeNodeView) view;
- TreeNode node = nodeView.getTreeElement();
- if (nodeView.getChildrenViews().size() == 0) {
- nodeView.setSpan(DIAMETER + NODE_GAP_HEIGHT);
- }
- else {
- if (nodeView.getChildrenViews().size() == 1) {
- TreeTransitionView childView = nodeView.getChildrenViews().get(0);
- calcSpan(childView);
- if (childView.getParentViews().size() > 1) {
- nodeView.setSpan(DIAMETER + NODE_GAP_HEIGHT);
- }
- else {
- nodeView.setSpan(childView.getSpan());
- }
- }
- else {
- DisjointSets branches = node.findMergingBranches();
- List children = node.getChildren();
-
- if (node == children.get(0).getParents().get(0)) {
- reorderBranches(node, branches);
- ArrayList newChildrenViews = new ArrayList<>();
- for (TreeTransition trans : node.getChildren()) {
- newChildrenViews.add((TreeTransitionView) viewMap.get(trans));
- }
- nodeView.setChildrenViews(newChildrenViews);
- }
-
- List> mergingSets = branches.getAllSets();
-
- double span = 0.0;
- for (Set mergeSet : mergingSets) {
- if (mergeSet.size() > 1) {
- TreeTransition mergePoint = TreeNode.findMergingPoint(mergeSet);
- TreeTransitionView mergePointView = (TreeTransitionView) viewMap.get(mergePoint);
- double subSpan = 0.0;
- for (TreeTransition branch : mergeSet) {
- TreeTransitionView branchView = (TreeTransitionView) viewMap.get(branch);
- subCalcSpan(branchView, mergePointView);
- subSpan += branchView.getSpan();
- }
- calcSpan(mergePointView);
- span += Math.max(mergePointView.getSpan(), subSpan);
- }
- else {
- TreeTransition trans = mergeSet.iterator().next();
- TreeTransitionView transView = (TreeTransitionView) viewMap.get(trans);
- calcSpan(transView);
- span += transView.getSpan();
- }
- }
- nodeView.setSpan(span);
- }
- }
- }
- else {
- TreeTransitionView transView = (TreeTransitionView) view;
- TreeNodeView nodeView = transView.getChildView();
- if (nodeView == null) {
- transView.setSpan(DIAMETER + NODE_GAP_HEIGHT);
- }
- else {
- calcSpan(nodeView);
- transView.setSpan(nodeView.getSpan());
- }
- }
- }
-
- /**
- * Calculates the sub span of a given sub tree rooted at the specified view and stops at the tree puzzleElement view
- * specified as stop. Stop tree puzzleElement is NOT included in the span calculation
- *
- * @param view
- * @param stop
- */
- private void subCalcSpan(TreeElementView view, TreeElementView stop) {
- //safe-guard for infinite loop
- if (view == stop) {
- return;
- }
-
- if (view.getType() == NODE) {
- TreeNodeView nodeView = (TreeNodeView) view;
- TreeNode node = nodeView.getTreeElement();
- if (nodeView.getChildrenViews().size() == 0) {
- nodeView.setSpan(DIAMETER + NODE_GAP_HEIGHT);
- }
- else {
- if (nodeView.getChildrenViews().size() == 1) {
- TreeTransitionView childView = nodeView.getChildrenViews().get(0);
- if (childView == stop) {
- nodeView.setSpan(DIAMETER + NODE_GAP_HEIGHT);
- }
- else {
- subCalcSpan(childView, stop);
- if (childView.getParentViews().size() > 1) {
- nodeView.setSpan(DIAMETER + NODE_GAP_HEIGHT);
- }
- else {
- nodeView.setSpan(childView.getSpan());
- }
- }
- }
- else {
- DisjointSets branches = node.findMergingBranches();
- List children = node.getChildren();
-
- if (node == children.get(0).getParents().get(0)) {
- reorderBranches(node, branches);
- }
-
- List> mergingSets = branches.getAllSets();
-
- double span = 0.0;
- for (Set mergeSet : mergingSets) {
- if (mergeSet.size() > 1) {
- TreeTransition mergePoint = TreeNode.findMergingPoint(mergeSet);
- TreeTransitionView mergePointView = (TreeTransitionView) viewMap.get(mergePoint);
- double subSpan = 0.0;
- for (TreeTransition branch : mergeSet) {
- TreeTransitionView branchView = (TreeTransitionView) viewMap.get(branch);
- subCalcSpan(branchView, mergePointView);
- subSpan += branchView.getSpan();
- }
- subCalcSpan(mergePointView, stop);
- span += Math.max(mergePointView.getSpan(), subSpan);
- }
- else {
- TreeTransition trans = mergeSet.iterator().next();
- TreeTransitionView transView = (TreeTransitionView) viewMap.get(trans);
- subCalcSpan(transView, stop);
- span += transView.getSpan();
- }
- }
-
- nodeView.setSpan(span);
- }
- }
- }
- else {
- TreeTransitionView transView = (TreeTransitionView) view;
- TreeNodeView nodeView = transView.getChildView();
- if (nodeView == null || nodeView == stop) {
- transView.setSpan(DIAMETER + NODE_GAP_HEIGHT);
- }
- else {
- calcSpan(nodeView);
- transView.setSpan(nodeView.getSpan());
- }
- }
- }
-
- /**
- * Reorders branches such that merging branches are sequentially grouped together and transitions are kept in
- * relative order in the list of child transitions of the specified node
- *
- * @param node root node of the branches
- * @param branches DisjointSets of the child branches of the specified node which determine which branches merge
- */
- private void reorderBranches(TreeNode node, DisjointSets branches) {
- List children = node.getChildren();
- List> mergingSets = branches.getAllSets();
-
- List> newOrder = new ArrayList<>();
- for (Set set : mergingSets) {
- List mergeBranch = new ArrayList<>();
- newOrder.add(mergeBranch);
- children.forEach(t -> {
- if (set.contains(t)) {
- mergeBranch.add(t);
- }
- });
- mergeBranch.sort((TreeTransition t1, TreeTransition t2) ->
- children.indexOf(t1) <= children.indexOf(t2) ? -1 : 1);
- }
-
- newOrder.sort((List b1, List b2) -> {
- int low1 = -1;
- int low2 = -1;
- for (TreeTransition t1 : b1) {
- int curIndex = children.indexOf(t1);
- if (low1 == -1 || curIndex < low1) {
- low1 = curIndex;
- }
- }
- for (TreeTransition t1 : b2) {
- int curIndex = children.indexOf(t1);
- if (low1 == -1 || curIndex < low1) {
- low1 = curIndex;
- }
- }
- return low1 < low2 ? -1 : 1;
- });
-
- List newChildren = new ArrayList<>();
- newOrder.forEach(l -> newChildren.addAll(l));
- node.setChildren(newChildren);
- }
+package edu.rpi.legup.ui.proofeditorui.treeview;
+
+import edu.rpi.legup.app.GameBoardFacade;
+import edu.rpi.legup.controller.TreeController;
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.GridCell;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.observer.ITreeListener;
+import edu.rpi.legup.model.rules.CaseRule;
+import edu.rpi.legup.model.rules.Rule;
+import edu.rpi.legup.model.tree.Tree;
+import edu.rpi.legup.model.tree.TreeElement;
+import edu.rpi.legup.model.tree.TreeNode;
+import edu.rpi.legup.model.tree.TreeTransition;
+import edu.rpi.legup.ui.ScrollView;
+import edu.rpi.legup.utility.DisjointSets;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.util.*;
+import java.util.List;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import static edu.rpi.legup.model.tree.TreeElementType.NODE;
+import static edu.rpi.legup.model.tree.TreeElementType.TRANSITION;
+import static edu.rpi.legup.ui.proofeditorui.treeview.TreeNodeView.DIAMETER;
+import static edu.rpi.legup.ui.proofeditorui.treeview.TreeNodeView.RADIUS;
+
+public class TreeView extends ScrollView implements ITreeListener {
+ private final static Logger LOGGER = LogManager.getLogger(TreeView.class.getName());
+
+ private static final int TRANS_GAP = 5;
+
+ private static final int NODE_GAP_WIDTH = 70;
+ private static final int NODE_GAP_HEIGHT = 15;
+
+ private static final int BORDER_GAP_HEIGHT = 20;
+ private static final int BORDER_GAP_WIDTH = 20;
+
+ private static final int BORDER_SPACING = 100;
+
+ private TreeNodeView nodeHover;
+
+ private ArrayList currentStateBoxes;
+ private Rectangle bounds = new Rectangle(0, 0, 0, 0);
+
+ private Tree tree;
+ private TreeNodeView rootNodeView;
+ private Map viewMap;
+ private Dimension dimension;
+
+ private TreeViewSelection selection;
+
+ public TreeView(TreeController treeController) {
+ super(treeController);
+ currentStateBoxes = new ArrayList<>();
+ setSize(dimension = new Dimension(100, 200));
+ setPreferredSize(new Dimension(640, 160));
+
+ viewMap = new HashMap<>();
+
+ selection = new TreeViewSelection();
+ }
+
+ public TreeViewSelection getSelection() {
+ return selection;
+ }
+
+ /**
+ * Gets the tree node puzzleElement that the mouse is hovering over
+ *
+ * @return tree node puzzleElement that the mouse is hovering over
+ */
+ public TreeNodeView getNodeHover() {
+ return nodeHover;
+ }
+
+ /**
+ * Sets the tree node puzzleElement that the mouse is hovering over
+ *
+ * @param nodeHover tree node puzzleElement the mouse is hovering over
+ */
+ public void setNodeHover(TreeNodeView nodeHover) {
+ this.nodeHover = nodeHover;
+ }
+
+ /**
+ * Gets the TreeElementView by the specified point or null if no view exists at the specified point
+ *
+ * @param point location to query for a view
+ * @return TreeElementView at the point specified, otherwise null
+ */
+ public TreeElementView getTreeElementView(Point point) {
+ return getTreeElementView(point, rootNodeView);
+ }
+
+ /**
+ * Recursively gets the TreeElementView by the specified point or null if no view exists at the specified point or
+ * the view specified is null
+ *
+ * @param point location to query for a view
+ * @param elementView view to determine if the point is contained within it
+ * @return TreeElementView at the point specified, otherwise null
+ */
+ private TreeElementView getTreeElementView(Point point, TreeElementView elementView) {
+ if (elementView == null) {
+ return null;
+ }
+ else {
+ if (elementView.contains(point) && elementView.isVisible()) {
+ if (elementView.getType() == NODE && ((TreeNodeView) elementView).isContradictoryState()) {
+ return null;
+ }
+ return elementView;
+ }
+ else {
+ if (elementView.getType() == NODE) {
+ TreeNodeView nodeView = (TreeNodeView) elementView;
+ for (TreeTransitionView transitionView : nodeView.getChildrenViews()) {
+ TreeElementView view = getTreeElementView(point, transitionView);
+ if (view != null) {
+ return view;
+ }
+ }
+ }
+ else {
+ TreeTransitionView transitionView = (TreeTransitionView) elementView;
+ return getTreeElementView(point, transitionView.getChildView());
+ }
+ }
+ }
+ return null;
+ }
+
+ public void updateTreeView(Tree tree) {
+ this.tree = tree;
+ if (selection.getSelectedViews().size() == 0) {
+ selection.newSelection(new TreeNodeView(tree.getRootNode()));
+ }
+ repaint();
+ }
+
+ /**
+ * Sets the tree associated with this TreeView
+ *
+ * @param tree tree
+ */
+ public void setTree(Tree tree) {
+ this.tree = tree;
+ }
+
+ public void updateTreeSize() {
+ if (GameBoardFacade.getInstance().getTree() == null) {
+ return;
+ }
+ setSize(bounds.getSize());
+ }
+
+ public void reset() {
+ if (bounds.x != 0 || bounds.y != 0) {
+ updateTreeSize();
+ }
+ }
+
+ public void zoomFit() {
+ double fitWidth = (viewport.getWidth() - 8.0) / (getSize().width - 200);
+ double fitHeight = (viewport.getHeight() - 8.0) / (getSize().height - 120);
+ zoomTo(Math.min(fitWidth, fitHeight));
+ viewport.setViewPosition(new Point(0, viewport.getHeight() / 2));
+ }
+
+ /**
+ * Creates a customized viewport for the scroll pane
+ *
+ * @return viewport for the scroll pane
+ */
+ @Override
+ protected JViewport createViewport() {
+ return new JViewport() {
+ @Override
+ protected LayoutManager createLayoutManager() {
+ return new ViewportLayout() {
+ @Override
+ public void layoutContainer(Container parent) {
+ Point point = viewport.getViewPosition();
+ // determine the maximum x and y view positions
+ int mx = getCanvas().getWidth() - viewport.getWidth();
+ int my = getCanvas().getHeight() - viewport.getHeight();
+ // obey edge boundaries
+ if (point.x < 0) {
+ point.x = 0;
+ }
+ if (point.x > mx) {
+ point.x = mx;
+ }
+ if (point.y < 0) {
+ point.y = 0;
+ }
+ if (point.y > my) {
+ point.y = my;
+ }
+ // center margins
+ if (mx < 0) {
+ point.x = 0;
+ }
+ if (my < 0) {
+ point.y = my / 2;
+ }
+ viewport.setViewPosition(point);
+ }
+ };
+ }
+ };
+ }
+
+ public void draw(Graphics2D graphics2D) {
+ currentStateBoxes.clear();
+ Tree tree = GameBoardFacade.getInstance().getTree();
+ if (tree != null) {
+ //setSize(bounds.getDimension());
+ graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ graphics2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+
+ drawTree(graphics2D);
+
+ dimension.width += BORDER_SPACING;
+ setSize(dimension);
+// graphics2D.drawRect(0,0, dimension.width, dimension.height);
+
+ if (selection.getHover() != null) {
+ drawMouseOver(graphics2D);
+ }
+ }
+ }
+
+ public void zoomReset() {
+ zoomTo(1.0);
+ viewport.setViewPosition(new Point(0, 0));
+ }
+
+ private void redrawTree(Graphics2D graphics2D, TreeNodeView nodeView) {
+ if (nodeView != null) {
+ nodeView.draw(graphics2D);
+ for (TreeTransitionView transitionView : nodeView.getChildrenViews()) {
+ transitionView.draw(graphics2D);
+ redrawTree(graphics2D, transitionView.getChildView());
+ }
+ }
+ }
+
+ public void removeTreeElement(TreeElementView view) {
+ if (view.getType() == NODE) {
+ TreeNodeView nodeView = (TreeNodeView) view;
+ nodeView.getParentView().setChildView(null);
+ }
+ else {
+ TreeTransitionView transitionView = (TreeTransitionView) view;
+ transitionView.getParentViews().forEach((TreeNodeView n) -> n.removeChildrenView(transitionView));
+ }
+ }
+
+ /**
+ * When the edu.rpi.legup.user hovers over the transition, draws the corresponding rules image
+ *
+ * @param g the graphics to use to draw
+ */
+ public void drawMouseOver(Graphics2D g) {
+ if (selection.getHover().getType() == TRANSITION && ((TreeTransitionView) selection.getHover()).getTreeElement().isJustified()) {
+ TreeTransition transition = (TreeTransition) selection.getHover().treeElement;
+ int imgWidth = 100;
+ int imgHeight = 100;
+
+ BufferedImage image = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_ARGB);
+ image.createGraphics().drawImage(transition.getRule().getImageIcon().getImage(), 0, 0, null);
+ Point mousePoint = selection.getMousePoint();
+ g.drawImage(image, mousePoint.x, mousePoint.y - 50, imgWidth, imgHeight, null);
+ }
+ }
+
+ public void resetView() {
+ this.tree = null;
+ this.rootNodeView = null;
+ this.selection.clearSelection();
+ this.selection.clearHover();
+ }
+
+ /**
+ * Called when a tree puzzleElement is added to the tree
+ *
+ * @param treeElement TreeElement that was added to the tree
+ */
+ @Override
+ public void onTreeElementAdded(TreeElement treeElement) {
+ if (treeElement.getType() == NODE) {
+ addTreeNode((TreeNode) treeElement);
+ }
+ else {
+ addTreeTransition((TreeTransition) treeElement);
+ }
+ repaint();
+ }
+
+ /**
+ * Called when a tree puzzleElement is removed from the tree
+ *
+ * @param element TreeElement that was removed to the tree
+ */
+ @Override
+ public void onTreeElementRemoved(TreeElement element) {
+ if (element.getType() == NODE) {
+ TreeNode node = (TreeNode) element;
+ TreeNodeView nodeView = (TreeNodeView) viewMap.get(node);
+
+ nodeView.getParentView().setChildView(null);
+ removeTreeNode(node);
+ }
+ else {
+ TreeTransition trans = (TreeTransition) element;
+ TreeTransitionView transView = (TreeTransitionView) viewMap.get(trans);
+
+ // unlock ancestor elements if case rule deleted
+ Rule rule = trans.getRule();
+ for (TreeNode node : trans.getParents()) {
+
+ // only if the last case of a case rule will be deleted
+ if (!(rule instanceof CaseRule && node.getChildren().isEmpty())) {
+ continue;
+ }
+
+ CaseRule caseRule = (CaseRule)rule;
+ // set dependent elements to be modifiable by ancestors (if not dependent on others)
+ List ancestors = node.getAncestors();
+ for (TreeNode ancestor : ancestors) {
+ // for all ancestors but root
+ if (ancestor.getParent() == null) {
+ continue;
+ }
+
+ for (PuzzleElement pelement : caseRule.dependentElements(node.getBoard(), trans.getSelection())) {
+ // decrement, unlock if 0 cases depended
+ PuzzleElement oldElement = ancestor.getParent().getBoard().getPuzzleElement(pelement);
+ oldElement.setCasesDepended(oldElement.getCasesDepended() - 1);
+ if (oldElement.getCasesDepended() != 0) {
+ continue;
+ }
+
+ // set modifiable if started modifiable
+ boolean modifiable = tree.getRootNode().getBoard().getPuzzleElement(oldElement).isModifiable();
+
+ // unmodifiable if already modified
+ TreeNode modNode = ancestor.getParent().getParents().get(0);
+ while (modNode.getParent()!=null) {
+ Board modBoard = modNode.getParent().getBoard();
+ if (modBoard.getModifiedData().contains(modBoard.getPuzzleElement(oldElement))) {
+ modifiable = false;
+ break;
+ }
+ modNode = modNode.getParent().getParents().get(0);
+ }
+ oldElement.setModifiable(modifiable);
+ }
+ }
+ }
+
+ transView.getParentViews().forEach(n -> n.removeChildrenView(transView));
+ removeTreeTransition(trans);
+ }
+ repaint();
+ }
+
+ /**
+ * Called when the tree selection was changed
+ *
+ * @param selection tree selection that was changed
+ */
+ @Override
+ public void onTreeSelectionChanged(TreeViewSelection selection) {
+ this.selection.getSelectedViews().forEach(v -> v.setSelected(false));
+ selection.getSelectedViews().forEach(v -> v.setSelected(true));
+ this.selection = selection;
+ repaint();
+ }
+
+ /**
+ * Called when the model has finished updating the tree.
+ */
+ @Override
+ public void onUpdateTree() {
+ repaint();
+ }
+
+ /**
+ * Gets the TreeElementView by the corresponding TreeElement associated with it
+ *
+ * @param element TreeElement of the view
+ * @return TreeElementView of the TreeElement associated with it
+ */
+ public TreeElementView getElementView(TreeElement element) {
+ return viewMap.get(element);
+ }
+
+ private void removeTreeNode(TreeNode node) {
+ viewMap.remove(node);
+ List children = node.getChildren();
+
+ // if child is a case rule, unlock ancestor elements
+ if (!children.isEmpty()) {
+ Rule rule = children.get(0).getRule();
+ if (rule instanceof CaseRule) {
+ CaseRule caseRule = (CaseRule)rule;
+ // set dependent elements to be modifiable by ancestors (if not dependent on others)
+ List ancestors = node.getAncestors();
+ for (TreeNode ancestor : ancestors) {
+ // for all ancestors but root
+ if (ancestor.getParent() == null) {
+ continue;
+ }
+ for (PuzzleElement pelement : caseRule.dependentElements(node.getBoard(), children.get(0).getSelection())) {
+ // decrement, unlock if 0 cases depended
+ PuzzleElement oldElement = ancestor.getParent().getBoard().getPuzzleElement(pelement);
+ oldElement.setCasesDepended(oldElement.getCasesDepended() - 1);
+ if (oldElement.getCasesDepended() == 0) {
+ continue;
+ }
+
+ // set modifiable if started modifiable
+ boolean modifiable = tree.getRootNode().getBoard().getPuzzleElement(oldElement).isModifiable();
+
+ // unmodifiable if already modified
+ TreeNode modNode = ancestor.getParent().getParents().get(0);
+ while (modNode.getParent() != null) {
+ Board modBoard = modNode.getParent().getBoard();
+ if (modBoard.getModifiedData().contains(modBoard.getPuzzleElement(oldElement))) {
+ modifiable = false;
+ break;
+ }
+ modNode = modNode.getParent().getParents().get(0);
+ }
+ oldElement.setModifiable(modifiable);
+ }
+ }
+ }
+ }
+ node.getChildren().forEach(t -> removeTreeTransition(t));
+ }
+
+ private void removeTreeTransition(TreeTransition trans) {
+ viewMap.remove(trans);
+ if (trans.getChildNode() != null) {
+ removeTreeNode(trans.getChildNode());
+ }
+ }
+
+ private void addTreeNode(TreeNode node) {
+ TreeTransition parent = node.getParent();
+
+ TreeNodeView nodeView = new TreeNodeView(node);
+ TreeTransitionView parentView = (TreeTransitionView) viewMap.get(parent);
+
+ nodeView.setParentView(parentView);
+ parentView.setChildView(nodeView);
+
+ viewMap.put(node, nodeView);
+
+ if (!node.getChildren().isEmpty()) {
+
+ // if adding a case rule, lock dependent ancestor elements
+ Rule rule = node.getChildren().get(0).getRule();
+ if (rule instanceof CaseRule) {
+ CaseRule caseRule = (CaseRule)rule;
+
+ List ancestors = node.getAncestors();
+ for (TreeNode ancestor : ancestors) {
+ // for all ancestors but root
+ if (ancestor.getParent() == null) {
+ continue;
+ }
+ for (PuzzleElement element : caseRule.dependentElements(node.getBoard(), node.getChildren().get(0).getSelection())) {
+ // increment and lock
+ PuzzleElement oldElement = ancestor.getParent().getBoard().getPuzzleElement(element);
+ oldElement.setCasesDepended(oldElement.getCasesDepended()+1);
+ oldElement.setModifiable(false);
+ }
+ }
+ }
+
+ node.getChildren().forEach(t -> addTreeTransition(t));
+ }
+ }
+
+ private void addTreeTransition(TreeTransition trans) {
+ List parents = trans.getParents();
+
+ TreeTransitionView transView = new TreeTransitionView(trans);
+ for (TreeNode parent : parents) {
+ TreeNodeView parentNodeView = (TreeNodeView) viewMap.get(parent);
+ transView.addParentView(parentNodeView);
+ parentNodeView.addChildrenView(transView);
+
+ // if transition is a new case rule, lock dependent ancestor elements
+ Rule rule = trans.getRule();
+ if (rule instanceof CaseRule && parent.getChildren().size()==1) {
+ CaseRule caseRule = (CaseRule)rule;
+
+ List ancestors = parent.getAncestors();
+ for (TreeNode ancestor : ancestors) {
+ // for all ancestors but root
+ if (ancestor.getParent() == null) {
+ continue;
+ }
+ for (PuzzleElement element : caseRule.dependentElements(parent.getBoard(), trans.getSelection())) {
+ // increment and lock
+ PuzzleElement oldElement = ancestor.getParent().getBoard().getPuzzleElement(element);
+ oldElement.setCasesDepended(oldElement.getCasesDepended()+1);
+ oldElement.setModifiable(false);
+ }
+ }
+ }
+ }
+
+ viewMap.put(trans, transView);
+
+ if (trans.getChildNode() != null) {
+ addTreeNode(trans.getChildNode());
+ }
+ }
+
+ ///New Draw Methods
+
+ public void drawTree(Graphics2D graphics2D) {
+ if (tree == null) {
+ LOGGER.error("Unable to draw tree.");
+ }
+ else {
+ if (rootNodeView == null) {
+ rootNodeView = new TreeNodeView(tree.getRootNode());
+
+ LOGGER.debug("Creating new views for tree view.");
+ createViews(rootNodeView);
+
+ selection.newSelection(rootNodeView);
+ }
+
+ dimension = new Dimension(0, 0);
+ calcSpan(rootNodeView);
+ rootNodeView.setSpan(rootNodeView.getSpan() + DIAMETER + BORDER_SPACING);
+
+ calculateViewLocations(rootNodeView, 0);
+ dimension.height = (int) rootNodeView.getSpan();
+
+ redrawTree(graphics2D, rootNodeView);
+ LOGGER.debug("DrawTree: dimensions - " + dimension.width + "x" + dimension.height);
+ }
+ }
+
+ public void createViews(TreeNodeView nodeView) {
+ if (nodeView != null) {
+ viewMap.put(nodeView.getTreeElement(), nodeView);
+
+ TreeNode node = nodeView.getTreeElement();
+ for (TreeTransition trans : node.getChildren()) {
+ TreeTransitionView transView = (TreeTransitionView) viewMap.get(trans);
+ if (transView != null) {
+ nodeView.addChildrenView(transView);
+ transView.addParentView(nodeView);
+ break;
+ }
+ transView = new TreeTransitionView(trans);
+
+ viewMap.put(transView.getTreeElement(), transView);
+
+ transView.addParentView(nodeView);
+ nodeView.addChildrenView(transView);
+
+ TreeNode childNode = trans.getChildNode();
+ if (childNode != null) {
+ TreeNodeView childNodeView = new TreeNodeView(childNode);
+ viewMap.put(childNodeView.getTreeElement(), childNodeView);
+
+ childNodeView.setParentView(transView);
+ transView.setChildView(childNodeView);
+
+ createViews(childNodeView);
+ }
+ }
+ }
+ }
+
+ public void calculateViewLocations(TreeNodeView nodeView, int depth) {
+ nodeView.setDepth(depth);
+ int xLoc = (NODE_GAP_WIDTH + DIAMETER) * depth + DIAMETER;
+ nodeView.setX(xLoc);
+ dimension.width = Math.max(dimension.width, xLoc);
+
+ TreeTransitionView parentTransView = nodeView.getParentView();
+ int yLoc = parentTransView == null ? (int) nodeView.getSpan() / 2 : parentTransView.getEndY();
+ nodeView.setY(yLoc);
+
+ ArrayList children = nodeView.getChildrenViews();
+ switch (children.size()) {
+ case 0:
+ break;
+ case 1: {
+ TreeTransitionView childView = children.get(0);
+
+ List parentsViews = childView.getParentViews();
+ if (parentsViews.size() == 1) {
+ childView.setEndY(yLoc);
+
+ childView.setDepth(depth);
+
+ Point lineStartPoint = childView.getLineStartPoint(0);
+ lineStartPoint.x = xLoc + RADIUS + TRANS_GAP / 2;
+ lineStartPoint.y = yLoc;
+ childView.setEndX((NODE_GAP_WIDTH + DIAMETER) * (depth + 1) + RADIUS - TRANS_GAP / 2);
+
+ dimension.width = Math.max(dimension.width, childView.getEndX());
+
+ TreeNodeView childNodeView = childView.getChildView();
+ if (childNodeView != null) {
+ calculateViewLocations(childNodeView, depth + 1);
+ }
+ }
+ else {
+ if (parentsViews.size() > 1 && parentsViews.get(parentsViews.size() - 1) == nodeView) {
+ int yAvg = 0;
+ for (int i = 0; i < parentsViews.size(); i++) {
+ TreeNodeView parentNodeView = parentsViews.get(i);
+ depth = Math.max(depth, parentNodeView.getDepth());
+ yAvg += parentNodeView.getY();
+
+ Point lineStartPoint = childView.getLineStartPoint(i);
+ lineStartPoint.x = parentNodeView.getX() + RADIUS + TRANS_GAP / 2;
+ lineStartPoint.y = parentNodeView.getY();
+ }
+ yAvg /= parentsViews.size();
+ childView.setEndY(yAvg);
+
+ childView.setDepth(depth);
+
+ childView.setEndX((NODE_GAP_WIDTH + DIAMETER) * (depth + 1) + RADIUS - TRANS_GAP / 2);
+
+ dimension.width = Math.max(dimension.width, childView.getEndX());
+
+ TreeNodeView childNodeView = childView.getChildView();
+ if (childNodeView != null) {
+ calculateViewLocations(childNodeView, depth + 1);
+ }
+ }
+ }
+ break;
+ }
+ default: {
+ int span = 0;
+ for (TreeTransitionView childView : children) {
+ span += childView.getSpan();
+ }
+
+ span = (int) ((nodeView.getSpan() - span) / 2);
+ for (int i = 0; i < children.size(); i++) {
+ TreeTransitionView childView = children.get(i);
+
+ childView.setDepth(depth);
+
+ Point lineStartPoint = childView.getLineStartPoint(0);
+ lineStartPoint.x = xLoc + RADIUS + TRANS_GAP / 2;
+ lineStartPoint.y = yLoc;
+ childView.setEndX((NODE_GAP_WIDTH + DIAMETER) * (depth + 1) + RADIUS - TRANS_GAP / 2);
+ childView.setEndY(yLoc - (int) (nodeView.getSpan() / 2) + span + (int) (childView.getSpan() / 2));
+
+ span += childView.getSpan();
+ TreeNodeView childNodeView = childView.getChildView();
+ if (childNodeView != null) {
+ calculateViewLocations(childNodeView, depth + 1);
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ public void calcSpan(TreeElementView view) {
+ if (view.getType() == NODE) {
+ TreeNodeView nodeView = (TreeNodeView) view;
+ TreeNode node = nodeView.getTreeElement();
+ if (nodeView.getChildrenViews().size() == 0) {
+ nodeView.setSpan(DIAMETER + NODE_GAP_HEIGHT);
+ }
+ else {
+ if (nodeView.getChildrenViews().size() == 1) {
+ TreeTransitionView childView = nodeView.getChildrenViews().get(0);
+ calcSpan(childView);
+ if (childView.getParentViews().size() > 1) {
+ nodeView.setSpan(DIAMETER + NODE_GAP_HEIGHT);
+ }
+ else {
+ nodeView.setSpan(childView.getSpan());
+ }
+ }
+ else {
+ DisjointSets branches = node.findMergingBranches();
+ List children = node.getChildren();
+
+ if (node == children.get(0).getParents().get(0)) {
+ reorderBranches(node, branches);
+ ArrayList newChildrenViews = new ArrayList<>();
+ for (TreeTransition trans : node.getChildren()) {
+ newChildrenViews.add((TreeTransitionView) viewMap.get(trans));
+ }
+ nodeView.setChildrenViews(newChildrenViews);
+ }
+
+ List> mergingSets = branches.getAllSets();
+
+ double span = 0.0;
+ for (Set mergeSet : mergingSets) {
+ if (mergeSet.size() > 1) {
+ TreeTransition mergePoint = TreeNode.findMergingPoint(mergeSet);
+ TreeTransitionView mergePointView = (TreeTransitionView) viewMap.get(mergePoint);
+ double subSpan = 0.0;
+ for (TreeTransition branch : mergeSet) {
+ TreeTransitionView branchView = (TreeTransitionView) viewMap.get(branch);
+ subCalcSpan(branchView, mergePointView);
+ subSpan += branchView.getSpan();
+ }
+ calcSpan(mergePointView);
+ span += Math.max(mergePointView.getSpan(), subSpan);
+ }
+ else {
+ TreeTransition trans = mergeSet.iterator().next();
+ TreeTransitionView transView = (TreeTransitionView) viewMap.get(trans);
+ calcSpan(transView);
+ span += transView.getSpan();
+ }
+ }
+ nodeView.setSpan(span);
+ }
+ }
+ }
+ else {
+ TreeTransitionView transView = (TreeTransitionView) view;
+ TreeNodeView nodeView = transView.getChildView();
+ if (nodeView == null) {
+ transView.setSpan(DIAMETER + NODE_GAP_HEIGHT);
+ }
+ else {
+ calcSpan(nodeView);
+ transView.setSpan(nodeView.getSpan());
+ }
+ }
+ }
+
+ /**
+ * Calculates the sub span of a given sub tree rooted at the specified view and stops at the tree puzzleElement view
+ * specified as stop. Stop tree puzzleElement is NOT included in the span calculation
+ *
+ * @param view
+ * @param stop
+ */
+ private void subCalcSpan(TreeElementView view, TreeElementView stop) {
+ //safe-guard for infinite loop
+ if (view == stop) {
+ return;
+ }
+
+ if (view.getType() == NODE) {
+ TreeNodeView nodeView = (TreeNodeView) view;
+ TreeNode node = nodeView.getTreeElement();
+ if (nodeView.getChildrenViews().size() == 0) {
+ nodeView.setSpan(DIAMETER + NODE_GAP_HEIGHT);
+ }
+ else {
+ if (nodeView.getChildrenViews().size() == 1) {
+ TreeTransitionView childView = nodeView.getChildrenViews().get(0);
+ if (childView == stop) {
+ nodeView.setSpan(DIAMETER + NODE_GAP_HEIGHT);
+ }
+ else {
+ subCalcSpan(childView, stop);
+ if (childView.getParentViews().size() > 1) {
+ nodeView.setSpan(DIAMETER + NODE_GAP_HEIGHT);
+ }
+ else {
+ nodeView.setSpan(childView.getSpan());
+ }
+ }
+ }
+ else {
+ DisjointSets branches = node.findMergingBranches();
+ List children = node.getChildren();
+
+ if (node == children.get(0).getParents().get(0)) {
+ reorderBranches(node, branches);
+ }
+
+ List> mergingSets = branches.getAllSets();
+
+ double span = 0.0;
+ for (Set mergeSet : mergingSets) {
+ if (mergeSet.size() > 1) {
+ TreeTransition mergePoint = TreeNode.findMergingPoint(mergeSet);
+ TreeTransitionView mergePointView = (TreeTransitionView) viewMap.get(mergePoint);
+ double subSpan = 0.0;
+ for (TreeTransition branch : mergeSet) {
+ TreeTransitionView branchView = (TreeTransitionView) viewMap.get(branch);
+ subCalcSpan(branchView, mergePointView);
+ subSpan += branchView.getSpan();
+ }
+ subCalcSpan(mergePointView, stop);
+ span += Math.max(mergePointView.getSpan(), subSpan);
+ }
+ else {
+ TreeTransition trans = mergeSet.iterator().next();
+ TreeTransitionView transView = (TreeTransitionView) viewMap.get(trans);
+ subCalcSpan(transView, stop);
+ span += transView.getSpan();
+ }
+ }
+
+ nodeView.setSpan(span);
+ }
+ }
+ }
+ else {
+ TreeTransitionView transView = (TreeTransitionView) view;
+ TreeNodeView nodeView = transView.getChildView();
+ if (nodeView == null || nodeView == stop) {
+ transView.setSpan(DIAMETER + NODE_GAP_HEIGHT);
+ }
+ else {
+ calcSpan(nodeView);
+ transView.setSpan(nodeView.getSpan());
+ }
+ }
+ }
+
+ /**
+ * Reorders branches such that merging branches are sequentially grouped together and transitions are kept in
+ * relative order in the list of child transitions of the specified node
+ *
+ * @param node root node of the branches
+ * @param branches DisjointSets of the child branches of the specified node which determine which branches merge
+ */
+ private void reorderBranches(TreeNode node, DisjointSets branches) {
+ List children = node.getChildren();
+ List> mergingSets = branches.getAllSets();
+
+ List> newOrder = new ArrayList<>();
+ for (Set set : mergingSets) {
+ List mergeBranch = new ArrayList<>();
+ newOrder.add(mergeBranch);
+ children.forEach(t -> {
+ if (set.contains(t)) {
+ mergeBranch.add(t);
+ }
+ });
+ mergeBranch.sort((TreeTransition t1, TreeTransition t2) ->
+ children.indexOf(t1) <= children.indexOf(t2) ? -1 : 1);
+ }
+
+ newOrder.sort((List b1, List b2) -> {
+ int low1 = -1;
+ int low2 = -1;
+ for (TreeTransition t1 : b1) {
+ int curIndex = children.indexOf(t1);
+ if (low1 == -1 || curIndex < low1) {
+ low1 = curIndex;
+ }
+ }
+ for (TreeTransition t1 : b2) {
+ int curIndex = children.indexOf(t1);
+ if (low1 == -1 || curIndex < low1) {
+ low1 = curIndex;
+ }
+ }
+ return low1 < low2 ? -1 : 1;
+ });
+
+ List newChildren = new ArrayList<>();
+ newOrder.forEach(l -> newChildren.addAll(l));
+ node.setChildren(newChildren);
+ }
}
\ No newline at end of file