diff --git a/puzzles files/skyscrapers/1646651 b/puzzles files/skyscrapers/1646651 new file mode 100644 index 000000000..847d8639c --- /dev/null +++ b/puzzles files/skyscrapers/1646651 @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/skyscrapers/easy1.xml b/puzzles files/skyscrapers/easy1.xml new file mode 100644 index 000000000..9d3135bff --- /dev/null +++ b/puzzles files/skyscrapers/easy1.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/edu/rpi/legup/history/AutoCaseRuleCommand.java b/src/main/java/edu/rpi/legup/history/AutoCaseRuleCommand.java index a4c157c77..331a3dddf 100644 --- a/src/main/java/edu/rpi/legup/history/AutoCaseRuleCommand.java +++ b/src/main/java/edu/rpi/legup/history/AutoCaseRuleCommand.java @@ -62,7 +62,6 @@ 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 3d84287e3..97c9205cf 100644 --- a/src/main/java/edu/rpi/legup/model/gameboard/PuzzleElement.java +++ b/src/main/java/edu/rpi/legup/model/gameboard/PuzzleElement.java @@ -11,7 +11,6 @@ public abstract class PuzzleElement { protected boolean isModified; protected boolean isGiven; protected boolean isValid; - protected int casesDepended; /** * PuzzleElement Constructor creates a new puzzle element. @@ -23,7 +22,6 @@ public PuzzleElement() { this.isModified = false; this.isGiven = false; this.isValid = true; - this.casesDepended = 0; } /** @@ -150,24 +148,6 @@ 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 efb96e21a..d9c7e73e5 100644 --- a/src/main/java/edu/rpi/legup/model/rules/CaseRule.java +++ b/src/main/java/edu/rpi/legup/model/rules/CaseRule.java @@ -7,8 +7,9 @@ import edu.rpi.legup.model.tree.TreeTransition; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; -import java.util.Set; +import java.util.Map; import static edu.rpi.legup.model.rules.RuleType.CASE; @@ -78,7 +79,6 @@ 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,31 +125,6 @@ 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 847764b7b..0465ca88c 100644 --- a/src/main/java/edu/rpi/legup/model/rules/DirectRule.java +++ b/src/main/java/edu/rpi/legup/model/rules/DirectRule.java @@ -33,10 +33,6 @@ 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 72572ac72..e1d042626 100644 --- a/src/main/java/edu/rpi/legup/model/tree/TreeTransition.java +++ b/src/main/java/edu/rpi/legup/model/tree/TreeTransition.java @@ -2,7 +2,6 @@ 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; @@ -13,8 +12,6 @@ public class TreeTransition extends TreeElement { private ArrayList parents; private TreeNode childNode; private Rule rule; - - private PuzzleElement selection; private boolean isCorrect; private boolean isVerified; @@ -29,7 +26,6 @@ public TreeTransition(Board board) { this.childNode = null; this.board = board; this.rule = null; - this.selection = null; this.isCorrect = false; this.isVerified = false; } @@ -91,42 +87,13 @@ 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) { - - // 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); - } - - // apply changes to result node - childNode.getBoard().notifyChange(copy); - - // apply to all child transitions + board.notifyChange(element); + childNode.getBoard().notifyChange(element.copy()); for (TreeTransition child : childNode.getChildren()) { - child.propagateChange(copy.copy()); + PuzzleElement copy = element.copy(); + copy.setModifiable(false); + child.propagateChange(copy); } } } @@ -360,27 +327,6 @@ 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 cf7b70ccd..1f166685b 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,67 +287,4 @@ 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 0e25586a8..2a40bf45d 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,137 +1,119 @@ -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; - } -} +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; + } +} 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 52e8a6400..ca6bcbe02 100644 --- a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersBoard.java +++ b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersBoard.java @@ -25,6 +25,9 @@ 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); @@ -91,6 +94,14 @@ 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 1e7b1b45e..dc68f45c7 100644 --- a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersClue.java +++ b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersClue.java @@ -47,9 +47,6 @@ public void setType(SkyscrapersType type) { } public SkyscrapersClue copy() { - SkyscrapersClue copy = new SkyscrapersClue(data, clueIndex, type); - copy.setIndex(index); - copy.setModifiable(isModifiable); - return copy; + return null; } } 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 01527294a..683b742bf 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,6 +69,7 @@ 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; @@ -102,7 +103,12 @@ public String checkRuleRaw(TreeTransition transition) { return "This case rule must have at least one child."; } - if (childTransitions.size() != getCasesFor(oldBoard, oldBoard.getPuzzleElement(transition.getSelection()), (Integer) childTransitions.get(0).getBoard().getModifiedData().iterator().next().getData()).size()) { + //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()) { //System.out.println("Wrong number of cases."); return "Wrong number of cases."; } @@ -126,49 +132,4 @@ 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 3bf0de70a..a061c62a3 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,7 +7,6 @@ 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.*; @@ -135,38 +134,4 @@ 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 dc809f34d..2542ea335 100644 --- a/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentBoard.java +++ b/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentBoard.java @@ -124,11 +124,21 @@ public void notifyDeletion(PuzzleElement puzzleElement) { public List getAdjacent(TreeTentCell cell, TreeTentType type) { List adj = new ArrayList<>(); Point loc = cell.getLocation(); - 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); - } + 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); } 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 698b3aa5e..538772b74 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,29 +160,4 @@ 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 39b1d0251..36f466f87 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,6 +5,7 @@ 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; @@ -150,18 +151,4 @@ 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 72ffd62eb..249547301 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,7 +10,6 @@ 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; @@ -153,32 +152,4 @@ 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 9bfffe60a..ecf59146d 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,886 +1,756 @@ -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); - } +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); + } } \ No newline at end of file