package com.baselet.element.relation.facet; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.baselet.control.SharedUtils; import com.baselet.control.basics.geom.Point; import com.baselet.control.basics.geom.PointDouble; import com.baselet.control.basics.geom.Rectangle; import com.baselet.control.enums.AlignHorizontal; import com.baselet.control.enums.Priority; import com.baselet.diagram.draw.DrawHandler; import com.baselet.element.facet.FirstRunFacet; import com.baselet.element.facet.KeyValueFacet; import com.baselet.element.facet.PropertiesParserState; import com.baselet.element.relation.helper.LineDescriptionEnum; import com.baselet.element.relation.helper.RelationPointHandler; import com.baselet.gui.AutocompletionText; /** * must be in first-run after LineDescriptionPositionFacet (because the displacement must be applied) * and before RelationLineTypeFacet and before drawCommonComponents (because the text changes the relationpoint placements and the size of the relation */ public class LineDescriptionFacet extends FirstRunFacet { static final int X_DIST_TO_LINE = 4; static final int LOWER_Y_DIST_TO_LINE = 1; static final int UPPER_Y_DIST_TO_LINE = 5; static final int MIDDLE_DISTANCE_TO_LINE = 4; public static final LineDescriptionFacet INSTANCE = new LineDescriptionFacet(); private LineDescriptionFacet() {} @Override public boolean checkStart(String line, PropertiesParserState state) { return !line.startsWith(RelationLineTypeFacet.KEY + KeyValueFacet.SEP); // because middle text has no prefix, apply alway except for the lt= line } @Override public List<AutocompletionText> getAutocompletionStrings() { return Arrays.asList( new AutocompletionText(LineDescriptionEnum.MESSAGE_START.getKey() + KeyValueFacet.SEP, "message at start"), new AutocompletionText(LineDescriptionEnum.MESSAGE_END.getKey() + KeyValueFacet.SEP, "message at end"), new AutocompletionText(LineDescriptionEnum.ROLE_START.getKey() + KeyValueFacet.SEP, "role at start"), new AutocompletionText(LineDescriptionEnum.ROLE_END.getKey() + KeyValueFacet.SEP, "role at end")); } @Override public void handleLine(String line, PropertiesParserState state) { // only act on parsingFinished() when all lines are known and other first-run-facets have been resolved (e.g. fg-color) } private RelationPointHandler getRelationPoints(PropertiesParserState state) { return ((SettingsRelation) state.getSettings()).getRelationPoints(); } private void addIndex(RelationPointHandler relationPoints, int index, Set<Integer> usedIndexes, Rectangle rect) { relationPoints.setTextBox(index, rect); usedIndexes.add(index); } @Override public void parsingFinished(PropertiesParserState state, List<String> handledLines) { Map<String, Point> displacements = state.getOrInitFacetResponse(LineDescriptionPositionFacet.class, new HashMap<String, Point>()); RelationPointHandler relationPoints = getRelationPoints(state); DrawHandler drawer = state.getDrawer(); Set<Integer> usedIndexes = new HashSet<Integer>(); List<String> middleLines = new ArrayList<String>(); List<String> otherLines = new ArrayList<String>(); for (String line : handledLines) { if (LineDescriptionEnum.forString(line) == LineDescriptionEnum.MESSAGE_MIDDLE) { middleLines.add(line); } else { otherLines.add(line); } } printMiddleDescription(relationPoints, drawer, usedIndexes, middleLines); printEndDescriptions(displacements, relationPoints, drawer, usedIndexes, otherLines); // all unused textboxes must be reset to default size (to make sure the relation size is correct even if LineDescriptionFacet is never called) relationPoints.resetTextBoxIndexesExcept(usedIndexes); relationPoints.resizeRectAndReposPoints(); // apply the (possible) changes now to make sure the following facets use correct coordinates } private void printMiddleDescription(RelationPointHandler relationPoints, DrawHandler drawer, Set<Integer> usedIndexes, List<String> middleLines) { double halfMiddleBlockHeight = middleLines.size() * drawer.textHeightMaxWithSpace() / 2; // because vertical text blocks should be centered, the half of the total text block must be subtracted Rectangle textSpace = null; for (int i = 0; i < middleLines.size(); i++) { String line = LineDescriptionUtils.replaceArrowsWithUtf8Characters(middleLines.get(i)); PointDouble pointText = LineDescriptionUtils.calcPosOfMiddleText(drawer, line, relationPoints.getMiddleLine(), i, halfMiddleBlockHeight); drawer.print(line, pointText, AlignHorizontal.LEFT); textSpace = increaseTextSpaceRectangleForLine(textSpace, drawer, line, pointText); } if (textSpace != null) { addIndex(relationPoints, LineDescriptionEnum.MESSAGE_MIDDLE.getIndex(), usedIndexes, textSpace); } } private void printEndDescriptions(Map<String, Point> displacements, RelationPointHandler relationPoints, DrawHandler drawer, Set<Integer> usedIndexes, List<String> otherLines) { for (String line : otherLines) { LineDescriptionEnum enumVal = LineDescriptionEnum.forString(line); String text = line.substring(line.indexOf(KeyValueFacet.SEP) + 1); if (!text.isEmpty()) { Rectangle textSpace = null; String[] splitAtLineEndChar = SharedUtils.splitAtLineEndChar(text); for (int i = 0; i < splitAtLineEndChar.length; i++) { // end description text can be split up with \n into multiple lines String subline = splitAtLineEndChar[i]; PointDouble pointText = LineDescriptionUtils.calcPosOfLineDescriptionText(drawer, subline, i, splitAtLineEndChar.length, relationPoints, enumVal); pointText = applyDisplacements(displacements, enumVal, pointText); drawer.print(subline, pointText, AlignHorizontal.LEFT); textSpace = increaseTextSpaceRectangleForLine(textSpace, drawer, subline, pointText); } addIndex(relationPoints, enumVal.getIndex(), usedIndexes, textSpace); } } } private PointDouble applyDisplacements(Map<String, Point> displacements, LineDescriptionEnum enumVal, PointDouble pointText) { Point displacement = displacements.get(enumVal.getKey()); if (displacement != null) { pointText = new PointDouble(pointText.getX() + displacement.getX(), pointText.getY() + displacement.getY()); } return pointText; } private Rectangle increaseTextSpaceRectangleForLine(Rectangle textSpaceRect, DrawHandler drawer, String line, PointDouble pointText) { Rectangle newSpaceRect = new Rectangle(pointText.getX(), pointText.getY() - drawer.textHeightMax(), drawer.textWidth(line), drawer.textHeightMax()); textSpaceRect = Rectangle.mergeToLeft(textSpaceRect, newSpaceRect); return textSpaceRect; } @Override public Priority getPriority() { return Priority.LOWEST; // because the middle text has no prefix, it should only apply after every other facet. also text DescriptionPositionFacet must be known before calculating the text position } }