de.pixida.logtest.designer.automaton.RectangularNode.java Source code

Java tutorial

Introduction

Here is the source code for de.pixida.logtest.designer.automaton.RectangularNode.java

Source

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright (c) 2016 Pixida GmbH
 */

package de.pixida.logtest.designer.automaton;

import java.util.Arrays;
import java.util.List;
import java.util.function.BiConsumer;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.json.JSONObject;

import de.pixida.logtest.designer.automaton.LineIntersector.Intersection;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;

class RectangularNode extends BaseNode {
    private static final Color BORDER_COLOR_DEFAULT = Color.BLACK;
    //    private static final Color BORDER_COLOR_SELECTED = Color.RED;
    private static final double DEFAULT_H = 50.0;
    private static final double DEFAULT_W = 90.0;
    private static final double DEFAULT_Y = 10.0;
    private static final double DEFAULT_X = 10.0;

    public enum ContentDisplayMode {
        OVERFLOW(true, (theTextFlow, theRectangle) -> {
            // Nothing to do
        }), CLIP(true, (theTextFlow, theRectangle) -> {
            final Rectangle clipRect = new Rectangle();
            clipRect.widthProperty().bind(theRectangle.widthProperty());
            clipRect.heightProperty().bind(theRectangle.heightProperty());
            theTextFlow.setClip(clipRect);
        }), ADJUST_RECT(false, (theTextFlow, theRectangle) -> {
            theRectangle.widthProperty().bind(theTextFlow.widthProperty());
            theRectangle.heightProperty().bind(theTextFlow.heightProperty());
        });

        private BiConsumer<TextFlow, Rectangle> initializer;
        private boolean resizable;

        private ContentDisplayMode(final boolean aResizable, final BiConsumer<TextFlow, Rectangle> aInitializer) {
            this.resizable = aResizable;
            this.initializer = aInitializer;
        }

        private void init(final TextFlow theTextFlow, final Rectangle theRectangle) {
            this.initializer.accept(theTextFlow, theRectangle);
        }

        private boolean isResizable() {
            return this.resizable;
        }
    }

    private static final String JSON_KEY_H = "h";
    private static final String JSON_KEY_W = "w";
    private static final String JSON_KEY_Y = "y";
    private static final String JSON_KEY_X = "x";

    private final ContentDisplayMode contentDisplayMode;

    private final Rectangle rectangle = new Rectangle();
    private final Text titleText = new Text();
    private final Text separatorText = new Text("\n");
    private final Text contentText = new Text();
    private final TextFlow textFlow = new TextFlow();

    private String title;
    private String content;
    private Color color;

    //    private Point2D originalClickedPoint;
    //    private Point2D originalRectPoint;

    //    private final Rectangle resizeSpotTopLeft = new Rectangle();
    //    private final Rectangle resizeSpotTopRight = new Rectangle();
    //    private final Rectangle resizeSpotBottomLeft = new Rectangle();
    //    private final Rectangle resizeSpotBottomRight = new Rectangle();
    //    private final Rectangle[] resizeSpots = {
    //                    this.resizeSpotTopLeft,
    //                    this.resizeSpotTopRight,
    //                    this.resizeSpotBottomLeft,
    //                    this.resizeSpotBottomRight
    //    };
    //    private Rectangle originalPosition;

    RectangularNode(final Graph graph, final ContentDisplayMode aContentDisplayMode, final int zIndex) {
        super(graph);

        Validate.notNull(aContentDisplayMode);
        this.contentDisplayMode = aContentDisplayMode;

        this.rectangle.setStroke(BORDER_COLOR_DEFAULT);

        final Font titleFont = Font.font(Font.getDefault().getFamily(), FontWeight.BOLD, 10.0);
        this.titleText.setFont(titleFont);

        final Font contentFont = Font.font(titleFont.getFamily(), FontWeight.NORMAL, titleFont.getSize());
        this.contentText.setFont(contentFont);

        this.textFlow.getChildren().addAll(this.titleText, this.separatorText, this.contentText);
        this.textFlow.layoutXProperty().bind(this.rectangle.xProperty());
        this.textFlow.layoutYProperty().bind(this.rectangle.yProperty());
        final double insetTop = 0.0;
        final double insetRight = 3.0;
        final double insetBottom = 3.0;
        final double insetLeft = 3.0;
        this.textFlow.setPadding(new Insets(insetTop, insetRight, insetBottom, insetLeft));
        this.textFlow.setMouseTransparent(true);

        this.contentDisplayMode.init(this.textFlow, this.rectangle);

        if (this.contentDisplayMode.isResizable()) {
            //            this.createResizeSpots();
        }

        this.registerPart(this.rectangle, zIndex);
        this.registerPart(this.textFlow, zIndex);
        this.registerActionHandler(this.rectangle);

        this.loadDimensionsFromJson(new JSONObject());

        this.setColor(Color.WHITE);

        this.setContent(null);
    }

    //    private void createResizeSpots()
    //    {
    //        final double rectOverlap = 4.0;
    //        this.resizeSpotTopLeft.layoutXProperty().bind(this.rectangle.xProperty().subtract(rectOverlap));
    //        this.resizeSpotTopLeft.layoutYProperty().bind(this.rectangle.yProperty().subtract(rectOverlap));
    //        this.resizeSpotTopRight.layoutXProperty()
    //            .bind(this.rectangle.xProperty().add(this.rectangle.widthProperty()).subtract(rectOverlap));
    //        this.resizeSpotTopRight.layoutYProperty().bind(this.rectangle.yProperty().subtract(rectOverlap));
    //        this.resizeSpotBottomLeft.layoutXProperty().bind(this.rectangle.xProperty().subtract(rectOverlap));
    //        this.resizeSpotBottomLeft.layoutYProperty().bind(
    //            this.rectangle.yProperty().add(this.rectangle.heightProperty()).subtract(rectOverlap));
    //        this.resizeSpotBottomRight.layoutXProperty().bind(
    //            this.rectangle.xProperty().add(this.rectangle.widthProperty()).subtract(rectOverlap));
    //        this.resizeSpotBottomRight.layoutYProperty().bind(
    //            this.rectangle.yProperty().add(this.rectangle.heightProperty()).subtract(rectOverlap));
    //        Arrays.stream(this.resizeSpots).forEach(dot -> {
    //            dot.setWidth(rectOverlap * 2.0);
    //            dot.setHeight(rectOverlap * 2.0);
    //        });
    //        Arrays.stream(this.resizeSpots).forEach(dot -> {
    //            dot.setFill(Color.RED);
    //        });
    //        Arrays.stream(this.resizeSpots).forEach(dot -> {
    //            dot.setOnMousePressed(event -> {
    //                this.originalPosition = new Rectangle();
    //                this.originalPosition.setX(this.rectangle.getX());
    //                this.originalPosition.setY(this.rectangle.getY());
    //                this.originalPosition.setWidth(this.rectangle.getWidth());
    //                this.originalPosition.setHeight(this.rectangle.getHeight());
    //                this.originalClickedPoint = new Point2D(event.getSceneX(), event.getSceneY());
    //            });
    //        });
    //        Arrays.stream(this.resizeSpots).forEach(dot -> {
    //            dot.setOnMouseDragged(event -> {
    //                final Point2D mouseMovement = new Point2D(event.getSceneX(), event.getSceneY()).subtract(this.originalClickedPoint);
    //                if (mouseMovement.getX() != 0.0 || mouseMovement.getY() != 0.0)
    //                {
    //                    this.onMoveOrResize();
    //
    //                    if (dot == this.resizeSpotTopLeft)
    //                    {
    //                        this.handleLeftResize(mouseMovement);
    //                        this.handleTopResize(mouseMovement);
    //                    }
    //                    else if (dot == this.resizeSpotBottomLeft)
    //                    {
    //                        this.handleLeftResize(mouseMovement);
    //                        this.handleBottomResize(mouseMovement);
    //                    }
    //                    else if (dot == this.resizeSpotTopRight)
    //                    {
    //                        this.handleRightResize(mouseMovement);
    //                        this.handleTopResize(mouseMovement);
    //                    }
    //                    else if (dot == this.resizeSpotBottomRight)
    //                    {
    //                        this.handleRightResize(mouseMovement);
    //                        this.handleBottomResize(mouseMovement);
    //                    }
    //                    else
    //                    {
    //                        // This should never happen
    //                    }
    //                }
    //            });
    //        });
    //    }

    String getTitle() {
        return this.title;
    }

    void setTitle(final String value) {
        this.title = value;
        this.titleText.setText(value);
        this.showTitleAndContentOnlyIfNotBlank();
        this.immediatelyApplyChangedSizeOfRectangle();
    }

    String getContent() {
        return this.content;
    }

    void setContent(final String value) {
        this.content = value;
        this.contentText.setText(value);
        this.showTitleAndContentOnlyIfNotBlank();
        this.immediatelyApplyChangedSizeOfRectangle();
    }

    private void showTitleAndContentOnlyIfNotBlank() {
        final boolean gotTitle = StringUtils.isNotBlank(this.title);
        final boolean gotContent = StringUtils.isNotBlank(this.content);

        this.textFlow.getChildren().clear();

        if (gotTitle) {
            this.textFlow.getChildren().add(this.titleText);
        }
        if (gotTitle && gotContent) {
            this.textFlow.getChildren().add(this.separatorText);
        }
        if (gotContent) {
            this.textFlow.getChildren().add(this.contentText);
        }
    }

    Color getColor() {
        return this.color;
    }

    void setColor(final Color value) {
        this.color = value;
        this.rectangle.setFill(value);
    }

    void loadDimensionsFromJson(final JSONObject jsonObject) {
        Validate.notNull(jsonObject);

        // Set default position and size here
        this.rectangle.setX(jsonObject.optDouble(JSON_KEY_X, DEFAULT_X));
        this.rectangle.setY(jsonObject.optDouble(JSON_KEY_Y, DEFAULT_Y));

        // Cannot set width and height when values are bound. Only set when the mode is CLIP or OVERFLOW
        if (this.contentDisplayMode.isResizable()) {
            this.rectangle.setWidth(jsonObject.optDouble(JSON_KEY_W, DEFAULT_W));
            this.rectangle.setHeight(jsonObject.optDouble(JSON_KEY_H, DEFAULT_H));
        }
    }

    void saveDimensionsToJson(final JSONObject rectConfig) {
        Validate.notNull(rectConfig);
        rectConfig.put(JSON_KEY_X, this.rectangle.getX());
        rectConfig.put(JSON_KEY_Y, this.rectangle.getY());
        rectConfig.put(JSON_KEY_W, this.rectangle.getWidth());
        rectConfig.put(JSON_KEY_H, this.rectangle.getHeight());
    }

    private Point2D getCenterPoint() {
        final double halfWay = 0.5;
        return new Point2D(this.rectangle.getX() + this.rectangle.getWidth() * halfWay,
                this.rectangle.getY() + this.rectangle.getHeight() * halfWay);
    }

    private List<Line> getBoundingLines() {
        final double l = this.rectangle.getX();
        final double t = this.rectangle.getY();
        final double b = t + this.rectangle.getHeight();
        final double r = l + this.rectangle.getWidth();
        final Point2D lt = new Point2D(l, t);
        final Point2D rt = new Point2D(r, t);
        final Point2D rb = new Point2D(r, b);
        final Point2D lb = new Point2D(l, b);
        return Arrays.asList(this.createLineBetweenTwoPoints(lt, rt), this.createLineBetweenTwoPoints(rt, rb),
                this.createLineBetweenTwoPoints(rb, lb), this.createLineBetweenTwoPoints(lb, lt));
    }

    private Line createLineBetweenTwoPoints(final Point2D from, final Point2D to) {
        return new Line(from.getX(), from.getY(), to.getX(), to.getY());
    }

    private void immediatelyApplyChangedSizeOfRectangle() {
        this.textFlow.autosize();
        this.realignEdgesAfterMoveOrResize();
    }

    @Override
    protected Point2D getConcreteDockingPointForConnection(final Point2D otherEnd) {
        final Point2D sourceToCenter = otherEnd.subtract(this.getCenterPoint());
        final List<Intersection> intersections = LineIntersector.calculateIntersections(otherEnd, sourceToCenter,
                this.getBoundingLines());
        if (intersections.isEmpty()) {
            return this.getCenterPoint();
        }
        return intersections.get(0).getIntersection();
    }

    @Override
    protected Point2D getDockingPointForConnection() {
        return this.getCenterPoint();
    }

    @Override
    protected Point2D getPosition() {
        return new Point2D(this.rectangle.getX(), this.rectangle.getY());
    }

    @Override
    protected void setPosition(final Point2D position) {
        this.rectangle.setX(Math.max(0d, position.getX()));
        this.rectangle.setY(Math.max(0d, position.getY()));
    }
}