package com.horstmann.violet.product.diagram.classes.node; import com.horstmann.violet.framework.graphics.content.*; import com.horstmann.violet.framework.graphics.shape.ContentInsideCustomShape; import com.horstmann.violet.framework.graphics.shape.ContentInsideEllipse; import com.horstmann.violet.product.diagram.abstracts.Direction; import com.horstmann.violet.product.diagram.abstracts.edge.IEdge; import com.horstmann.violet.product.diagram.common.node.ColorableNode; import com.horstmann.violet.product.diagram.abstracts.node.INode; import com.horstmann.violet.product.diagram.classes.ClassDiagramConstant; import com.horstmann.violet.product.diagram.property.choiceList.ChoiceList; import com.horstmann.violet.product.diagram.property.choiceList.TextChoiceList; import com.horstmann.violet.product.diagram.property.text.LineText; import com.horstmann.violet.product.diagram.property.text.SingleLineText; import java.awt.*; import java.awt.geom.*; import static com.horstmann.violet.product.diagram.classes.node.BallAndSocketNode.Types.BALL; /** * This class represents ball and socket (Ball and Socket interface notification) * * @author Jakub Homlala */ public class BallAndSocketNode extends ColorableNode { protected enum Types { BALL, SOCKET, BALL_AND_SOCKET } protected class SocketShape implements ContentInsideCustomShape.ShapeCreator { @Override public Shape createShape(double contentWidth, double contentHeight) { int angle = orientation.getSelectedValue(); return new Arc2D.Double(0, 0, DEFAULT_DIAMETER, DEFAULT_DIAMETER, angle, 180, Arc2D.OPEN); } } /** * Constructor, setting default values for properties, setting colors of * node. */ public BallAndSocketNode() { super(); name = new SingleLineText(); name.setPadding(5,5,5,5); type = new TextChoiceList<Types>(TYPE_KEYS, TYPE_VALUES); orientation = new TextChoiceList<Integer>(ORIENTATION_KEYS, ORIENTATION_VALUES); this.selectedType = this.type.getSelectedPos(); this.selectedOrientation = this.orientation.getSelectedPos(); } protected BallAndSocketNode(BallAndSocketNode node) throws CloneNotSupportedException { super(node); name = node.name.clone(); type = node.type.clone(); orientation = node.orientation.clone(); name.setPadding(5,5,5,5); this.selectedType = this.type.getSelectedPos(); this.selectedOrientation = this.orientation.getSelectedPos(); createContentStructure(); } @Override protected void beforeReconstruction() { super.beforeReconstruction(); if(null == name) { name = new SingleLineText(); } name.reconstruction(); name.setPadding(5,5,5,5); type = new TextChoiceList<Types>(TYPE_KEYS, TYPE_VALUES); orientation = new TextChoiceList<Integer>(ORIENTATION_KEYS, ORIENTATION_VALUES); type.setSelectedIndex(selectedType); orientation.setSelectedIndex(selectedOrientation); } @Override protected INode copy() throws CloneNotSupportedException { return new BallAndSocketNode(this); } @Override public void createContentStructure() { VerticalLayout verticalGroupContent = new VerticalLayout(); ballAndSocketLayout = new RelativeLayout(); ballAndSocketLayout.setMinWidth(DEFAULT_DIAMETER); ballAndSocketLayout.setMinHeight(DEFAULT_DIAMETER); refreshBallAndSocketLayout(); TextContent nameContent = new TextContent(name); verticalGroupContent.add(new CenterContent(ballAndSocketLayout)); verticalGroupContent.add(nameContent); setContent(verticalGroupContent); setTextColor(super.getTextColor()); } @Override public void setBackgroundColor(Color bgColor) { super.setBackgroundColor(bgColor); ballBackground.setBackgroundColor(bgColor); } @Override public void setBorderColor(Color borderColor) { super.setBorderColor(borderColor); socketBorder.setBorderColor(borderColor); ballBorder.setBorderColor(borderColor); } @Override public String getToolTip() { return ClassDiagramConstant.CLASS_DIAGRAM_RESOURCE.getString("tooltip.ball_and_socket_node"); } @Override public Point2D getConnectionPoint(IEdge edge) { Direction direction = edge.getDirection(this).getNearestCardinalDirection(); Rectangle2D selfBounds = getBounds(); if(!name.toEdit().isEmpty() && Direction.NORTH.equals(direction)) { return new Point2D.Double( selfBounds.getCenterX(), selfBounds.getMaxY() ); } Types type = (Types)getType().getSelectedValue(); int orientationAngle = orientation.getSelectedValue(); int directionAngle = 0; if(Direction.SOUTH.equals(direction)) { directionAngle = 180; } else if(Direction.WEST.equals(direction)) { directionAngle = 90; } else if(Direction.EAST.equals(direction)) { directionAngle = 270; } boolean supportDirection =(Types.BALL_AND_SOCKET == type && (directionAngle +180)%360 == orientationAngle); if(supportDirection) { orientationAngle +=180; } if(Types.SOCKET == type || supportDirection) { double radians = Math.toRadians(orientationAngle); return new Point2D.Double( selfBounds.getCenterX() + Math.sin(radians) * DEFAULT_DIAMETER/2, selfBounds.getY() + (1+Math.cos(radians)) * (DEFAULT_DIAMETER)/2 ); } double topGap = 0; if(0 == directionAngle) { topGap = -DEFAULT_GAP; } else if(180 == directionAngle) { topGap = DEFAULT_GAP; } double radians = Math.toRadians(directionAngle); return new Point2D.Double( selfBounds.getCenterX() + Math.sin(radians) * (DEFAULT_DIAMETER - DEFAULT_GAP)/2, selfBounds.getY() + topGap + (1+Math.cos(radians)) * DEFAULT_DIAMETER/2 ); } private void refreshBallAndSocketLayout() { ballAndSocketLayout.remove(ballBackground); ballAndSocketLayout.remove(socketBorder); Types type = (Types)getType().getSelectedValue(); if(Types.BALL_AND_SOCKET == type || Types.SOCKET == type) { Content content = new EmptyContent(); content.setMinWidth(DEFAULT_DIAMETER); content.setMinHeight(DEFAULT_DIAMETER); socketBorder = new ContentBorder( new ContentInsideCustomShape(content, new SocketShape()), getBorderColor() ); ballAndSocketLayout.add(socketBorder); } if(Types.BALL_AND_SOCKET == type || Types.BALL == type) { Content content = new EmptyContent(); content.setMinWidth((DEFAULT_DIAMETER-2*DEFAULT_GAP)/Math.sqrt(2)); content.setMinHeight((DEFAULT_DIAMETER-2*DEFAULT_GAP)/Math.sqrt(2)); ballBorder = new ContentBorder(new ContentInsideEllipse(content, 1), getBorderColor()); ballBackground = new ContentBackground(ballBorder, getBackgroundColor()); ballAndSocketLayout.add(ballBackground, new Point2D.Double(DEFAULT_GAP,DEFAULT_GAP)); } } /** * Sets the name property value. * * @param newValue * the new state name */ public void setName(LineText newValue) { name.setText(newValue); } /** * Gets the name property value. * * @return the state name */ public LineText getName() { return name; } /** * This getter is used for violet framework in order to make type property * visible in property editor. Get choice type list used in node. * * @return orientation choice list */ public ChoiceList getOrientation() { return orientation; } /** * This setter is used for violet framework in order to make type property * visible in property editor. Set node type choice list. * * @param orientation */ public void setOrientation(ChoiceList orientation) { if(this.orientation.setSelectedIndex(orientation.getSelectedPos())) { this.selectedOrientation = orientation.getSelectedPos(); refreshBallAndSocketLayout(); } } public ChoiceList getType() { return type; } public void setType(ChoiceList type) { if(this.type.setSelectedIndex(type.getSelectedPos())) { this.selectedType = type.getSelectedPos(); refreshBallAndSocketLayout(); } } private SingleLineText name; private int selectedOrientation; private int selectedType; private transient TextChoiceList<Integer> orientation; private transient TextChoiceList<Types> type; private transient RelativeLayout ballAndSocketLayout; private transient ContentBorder socketBorder; private transient ContentBorder ballBorder; private transient ContentBackground ballBackground; /** * Diameter of circle used for creating bounds */ private static final int DEFAULT_DIAMETER = 30; /** * Gap of circle used for creating bounds */ private static final int DEFAULT_GAP = 5; public static String[] ORIENTATION_KEYS = new String[]{"top", "bottom", "left", "right"}; public static final Integer[] ORIENTATION_VALUES = new Integer[]{0, 180, 270, 90}; public static String[] TYPE_KEYS = new String[]{"ball_and_socket", "ball", "socket" }; public static final Types[] TYPE_VALUES = new Types[]{Types.BALL_AND_SOCKET, BALL, Types.SOCKET}; static { for(int i=0; i< ORIENTATION_KEYS.length; ++i) { try { ORIENTATION_KEYS[i] = ClassDiagramConstant.CLASS_DIAGRAM_RESOURCE.getString( ("orientation." + ORIENTATION_KEYS[i]).toLowerCase() ); }catch (Exception ignored){} } for(int i=0; i< TYPE_KEYS.length; ++i) { try { TYPE_KEYS[i] = ClassDiagramConstant.CLASS_DIAGRAM_RESOURCE.getString( ("type." + TYPE_KEYS[i]).toLowerCase() ); }catch (Exception ignored){} } } }