All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eu.mihosoft.vrl.workflow.fx.FlowNodeWindow Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2012-2021 Michael Hoffer . All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * Please cite the following publication(s):
 *
 * M. Hoffer, C.Poliwoda, G.Wittum. Visual Reflection Library -
 * A Framework for Declarative GUI Programming on the Java Platform.
 * Computing and Visualization in Science, 2011, in press.
 *
 * THIS SOFTWARE IS PROVIDED BY Michael Hoffer  "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Michael Hoffer  OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and should not be interpreted as representing official policies, either expressed
 * or implied, of Michael Hoffer .
 */
package eu.mihosoft.vrl.workflow.fx;

import eu.mihosoft.vrl.workflow.Connection;
import eu.mihosoft.vrl.workflow.Connections;
import eu.mihosoft.vrl.workflow.Connector;
import eu.mihosoft.vrl.workflow.VFlow;
import eu.mihosoft.vrl.workflow.VFlowModel;
import eu.mihosoft.vrl.workflow.VNode;
import eu.mihosoft.vrl.workflow.skin.ConnectionSkin;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javafx.beans.InvalidationListener;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;

import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;

import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;

import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
import jfxtras.scene.control.window.CloseIcon;
import jfxtras.scene.control.window.MinimizeIcon;
import jfxtras.scene.control.window.Window;
import jfxtras.scene.control.window.WindowIcon;
//import jfxtras.labs.scene.control.window.CloseIcon;
//import jfxtras.labs.scene.control.window.MinimizeIcon;
//import jfxtras.labs.scene.control.window.Window;
//import jfxtras.labs.scene.control.window.WindowIcon;
import jfxtras.scene.control.window.WindowUtil;


/**
 * A window that represents a flow node.
 *
 * @author Michael Hoffer <[email protected]>
 */
public final class FlowNodeWindow extends Window {

    private final ObjectProperty nodeSkinProperty
            = new SimpleObjectProperty<>();
    private VCanvas content;
    private Pane inputContainer;
    private Pane outputContainer;
    private OptimizableContentPane parentContent;
    private final CloseIcon closeIcon = new CloseIcon(this);
    private final MinimizeIcon minimizeIcon = new MinimizeIcon(this);

    private ChangeListener selectionListener;

    private Callback showCloseIcon = (FlowNodeWindow w) -> {
        if (!getLeftIcons().contains(closeIcon)) {
            getLeftIcons().add(closeIcon);
        }

        return closeIcon;
    };

    private Callback hideCloseIcon = (FlowNodeWindow w) -> {

        getLeftIcons().remove(closeIcon);

        return closeIcon;
    };

    private Callback showMinimizeIcon = (FlowNodeWindow w) -> {
        if (!getLeftIcons().contains(minimizeIcon)) {
            getLeftIcons().add(minimizeIcon);
        }

        return minimizeIcon;
    };

    private Callback hideMinimizeIcon = (FlowNodeWindow w) -> {

        getLeftIcons().remove(minimizeIcon);

        return minimizeIcon;
    };

    /**
     * Construxtor.
     *
     * @param skin the skin of the node that shall be visualized by this window.
     */
    public FlowNodeWindow(final FXFlowNodeSkin skin) {

        nodeSkinProperty().set(skin);
        setEditableState(true);

        initUI(skin);
        initListenersAndBindings(skin);
        initCaching();
    }

    private void initUI(final FXFlowNodeSkin skin) {
        // only register content if this window visualizes a flow
        if (skin.getModel() instanceof VFlowModel) {

            VFlowModel flowNodeModel = (VFlowModel) skin.getModel();

            parentContent = new OptimizableContentPane();
            content = new VCanvas();
            content.setPadding(new Insets(5));
            content.setMinScaleX(0.01);
            content.setMinScaleY(0.01);
            content.setMaxScaleX(1);
            content.setMaxScaleY(1);

            HBox.setHgrow(content, Priority.SOMETIMES);

            addResetViewMenu(content);

            inputContainer = new VBox();
            outputContainer = new VBox();
            HBox paramBox = new HBox(inputContainer, content, getOutputContainer());

            parentContent.getChildren().add(paramBox);
            super.setContentPane(parentContent);

            InvalidationListener refreshViewListener = (o) -> {
                content.resetScale();
                content.resetTranslation();
            };
        }

        // 
        addCollapseIcon(skin);
        configureCanvas(skin);
    }

    private void initListenersAndBindings(final FXFlowNodeSkin skin) {
        addEventHandler(MouseEvent.MOUSE_ENTERED, (MouseEvent t) -> {
            connectorsToFront();
        });

        setSelectable(skin.getModel().isSelectable());
        skin.getModel().selectableProperty().bindBidirectional(this.selectableProperty());

        WindowUtil.getDefaultClipboard().select(FlowNodeWindow.this, skin.getModel().isSelected());

        selectionListener = (ov, oldValue, newValue) -> {
            WindowUtil.getDefaultClipboard().select(FlowNodeWindow.this, newValue);
        };

        skin.getModel().selectedProperty().addListener(selectionListener);

        skin.getModel().requestSelection(FlowNodeWindow.this.isSelected());
        FlowNodeWindow.this.selectedProperty().addListener((ov, oldValue, newValue) -> {
            skin.getModel().requestSelection(newValue);
        });

        skinProperty().addListener((ov, oldValue, newValue) -> {
            if (newValue != null) {
                Node titlebar = newValue.getNode().lookup("." + getTitleBarStyleClass());

                titlebar.addEventHandler(MouseEvent.ANY, (MouseEvent evt) -> {
                    if (evt.getClickCount() == 1
                            && evt.getEventType() == MouseEvent.MOUSE_RELEASED
                            && evt.isDragDetect()) {
                        skin.getModel().requestSelection(!skin.getModel().isSelected());
                    }
                });
            }
        });

        onClosedActionProperty().addListener((ov, oldValue, newValue) -> {
            onRemovedFromSceneGraph();
        });

//        // TODO shouldn't leaf nodes also have a visibility property?
//        if (nodeSkinProperty.get().getModel() instanceof VFlowModel) {
//            VFlowModel model = (VFlowModel) nodeSkinProperty.get().getModel();
//            model.visibleProperty().addListener(new ChangeListener() {
//                @Override
//                public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) {
//                    
//                    for (Node n : content.getContentPane().getChildren()) {
//                        if (n instanceof Window) {
//                            Window w = (Window) n;
//                            w.requestLayout();
//                            w.getContentPane().requestLayout();
//                        }
//                    }
//                    
////                    System.out.println("TEST");
//////                    parentContent.requestOptimization();
////                    requestLayout();
////                    getParent().requestLayout();
//////                    requestParentLayout();
////                    content.requestLayout();
////                    content.getContentPane().requestLayout();
//                }
//            });
//        }
    }

    ObservableList getChildrenModifiable() {
        return super.getChildren();
    }

    public static void addResetViewMenu(VCanvas canvas) {
        final ContextMenu cm = new ContextMenu();
        MenuItem resetViewItem = new MenuItem("Reset View");
        resetViewItem.setOnAction((ActionEvent e) -> {
            canvas.resetTranslation();
            canvas.resetScale();
        });
        cm.getItems().add(resetViewItem);
        canvas.addEventHandler(MouseEvent.MOUSE_CLICKED, (MouseEvent e) -> {
            if (e.getButton() == javafx.scene.input.MouseButton.SECONDARY) {
                cm.show(canvas, e.getScreenX(), e.getScreenY());
            }
        });
    }

    private void showFlowInWindow(VFlow flow, List stylesheets, String title) {

        // create scalable root pane
        VCanvas canvas = new VCanvas();
        addResetViewMenu(canvas);
        canvas.setMinScaleX(0.2);
        canvas.setMinScaleY(0.2);
        canvas.setMaxScaleX(1);
        canvas.setMaxScaleY(1);

        canvas.setTranslateToMinNodePos(true);

        // create skin factory for flow visualization
        FXSkinFactory fXSkinFactory
                = nodeSkinProperty.get().getSkinFactory().
                newInstance((Parent) canvas.getContent(), null);

        // generate the ui for the flow
        flow.addSkinFactories(fXSkinFactory);

        // the usual stage/scene setup
        Scene scene = new Scene(canvas, 800, 800);
        scene.getStylesheets().setAll(stylesheets);

        VFlow rootFlow = flow.getRootFlow();
        Stage stage = new FlowStage(rootFlow, this, canvas);

        stage.setWidth(800);
        stage.setHeight(600);

        stage.setTitle(title);
        stage.setScene(scene);
        stage.show();
    }

    public Pane getWorkflowContentPane() {
        return (Pane) content.getContent();
    }

    /**
     * @return the nodeSkinProperty
     */
    public final ObjectProperty nodeSkinProperty() {
        return nodeSkinProperty;
    }

    private void addCollapseIcon(FXFlowNodeSkin skin) {

        if (skin == null) {
            return;
        }

        if (!(skin.getModel() instanceof VFlowModel)) {
            return;
        }

        final WindowIcon collapseIcon = new WindowIcon();

        collapseIcon.setOnAction((ActionEvent t) -> {
            FXFlowNodeSkin skin1 = nodeSkinProperty.get();
            if (skin1 != null) {
                VFlowModel model = (VFlowModel) skin1.getModel();
                model.setVisible(!model.isVisible());
            }
        });

        getRightIcons().add(collapseIcon);

        if (skin.modelProperty() != null) {
            skin.modelProperty().addListener((ov, oldValue, newValue) -> {
                if (newValue instanceof VFlowModel) {
                    getRightIcons().add(collapseIcon);
                } else {
                    getRightIcons().remove(collapseIcon);
                }
            });
        }

        // adds an icon that opens a new view in a separate window
        final WindowIcon newViewIcon = new WindowIcon();

        newViewIcon.setOnAction((ActionEvent t) -> {
            FXFlowNodeSkin skin1 = nodeSkinProperty.get();
            if (skin1 != null) {
                String nodeId = skin1.getModel().getId();
                for (VFlow vf : skin1.getController().getSubControllers()) {
                    if (vf.getModel().getId().equals(nodeId)) {
                        showFlowInWindow(vf,
                                NodeUtil.getStylesheetsOfAncestors(
                                        FlowNodeWindow.this),
                                getLocation(vf));
                        break;
                    }
                }
            }
        });

        getLeftIcons().add(newViewIcon);

    }

    private String getLocation(VFlow f) {

        VFlowModel parent = f.getModel().getFlow();

        List names = new ArrayList<>();

        names.add(f.getModel().getTitle());

        while (parent != null) {
            names.add(parent.getTitle());
            parent = parent.getFlow();
        }

        Collections.reverse(names);

        StringBuilder sb = new StringBuilder();

        names.forEach(n -> sb.append("/").append(n));

        return sb.toString();
    }

    @Override
    public void toFront() {
        super.toFront();
        connectorsToFront();
    }

    private void connectorsToFront() {
        // move connectors to front
        FXFlowNodeSkin skin = nodeSkinProperty().get();

        for (List shapeList : skin.shapeLists) {
            for (ConnectorShape cs : shapeList) {
                cs.getNode().toFront();
            }
        }

        List connections = new ArrayList<>();

        for (Connector connector : skin.connectors.keySet()) {
            for (Connections connectionsI : skin.controller.getAllConnections().values()) {
                connections.addAll(connectionsI.getAllWith(connector));
            }
        }

        for (Connection conn : connections) {
            ConnectionSkin skinI = skin.controller.getNodeSkinLookup().
                    getById(skin.getSkinFactory(), conn);

            if (skinI instanceof FXConnectionSkin) {
                FXConnectionSkin fxSkin = (FXConnectionSkin) skinI;
                fxSkin.receiverToFront();
            }
        }
    }

    private void configureCanvas(FXFlowNodeSkin skin) {

//        if (skin == null) {
//            return;
//        }
//
//        if ((skin.getModel() instanceof VFlowModel)) {
//            return;
//        }
        if (content != null) {
            content.getStyleClass().setAll("vnode-content");
            skin.configureCanvas(content);
        }
    }

    void onRemovedFromSceneGraph() {
        nodeSkinProperty().get().getModel().selectedProperty().
                removeListener(selectionListener);
    }

    private void setCloseableState(boolean b) {
        if (b) {
            getShowCloseIconCallback().call(this);
        } else {
            getHideCloseIconCallback().call(this);
        }
    }

    private void setMinimizableState(boolean b) {
        if (b) {
            getShowMinimizeIconCallback().call(this);
        } else {
            getHideMinimizeIconCallback().call(this);
        }
    }

    final void setEditableState(boolean b) {
        setCloseableState(b);
        setMinimizableState(b);
//        setSelectable(b);
    }

    /**
     * @return the paramContainer
     */
    public Pane getInputContainer() {
        return inputContainer;
    }

    /**
     * @return the outputContainer
     */
    public Pane getOutputContainer() {
        return outputContainer;
    }

    private Callback getShowCloseIconCallback() {
        return showCloseIcon;
    }

    public void setShowCloseIconCallback(Callback callback) {
        this.showCloseIcon = callback;
    }

    private Callback getHideCloseIconCallback() {
        return hideCloseIcon;
    }

    public void setHideCloseIconCallback(Callback callback) {
        this.hideCloseIcon = callback;
    }

    private Callback getShowMinimizeIconCallback() {
        return showMinimizeIcon;
    }

    public void setShowMinimizeIconCallback(Callback callback) {
        this.showMinimizeIcon = callback;
    }

    private Callback getHideMinimizeIconCallback() {
        return hideMinimizeIcon;
    }

    public void setHideMinimizeIconCallback(Callback callback) {
        this.hideMinimizeIcon = callback;
    }

    private void initCaching() {
        
        localToSceneTransformProperty().addListener((ov)->{
             Bounds bounds = this.localToScene(getBoundsInLocal());
             if(bounds.getWidth()<10 || bounds.getHeight()<10) {
                 setCache(false);
             } else {
                 setCache(true);
             }
        });

//
//        boolean[] wasMoving = {false};
//
//        InvalidationListener cacheListener = (ov) -> {
//            
//            if (isMoving()) {
//                setCache(true);
//                Parent parent = getParent();
//                if (parent != null) {
//                    parent.getChildrenUnmodifiable().stream().
//                            filter(n -> n instanceof FlowNodeWindow
//                                    || n instanceof ConnectorCircle).
//                            forEach(n -> n.setCache(true));
//                }
//            } else {
//                setCache(false);
//                System.out.println("was: " + wasMoving[0]);
//                if (wasMoving[0]) {
//                    Parent parent = getParent();
//                    if (parent != null) {
//                        parent.getChildrenUnmodifiable().stream().
//                                filter(n -> n instanceof FlowNodeWindow
//                                        || n instanceof ConnectorCircle).
//                                forEach(n -> n.setCache(false));
//                    }
//                }
//            }
//            
//            wasMoving[0] = isMoving();
//        };
//
//        movingProperty().addListener(cacheListener);
//
//        cacheProperty().addListener(state -> setTitle("cache: " + isCache()));
    }

    static class FlowStage extends Stage {

        public FlowStage(VFlow rootFlow, FlowNodeWindow flowNodeWindow, VCanvas canvas) {
            String nodeId = flowNodeWindow.
                    nodeSkinProperty().get().getModel().getId();

            Stage stage = this;
            rootFlow.getNodes().addListener(
                    (ListChangeListener.Change c) -> {
                        while (c.next()) {
                            if (c.wasAdded()) {
                                for (VNode n : c.getAddedSubList()) {
                                    if (n.getId().equals(nodeId)) {
                                        ((Pane)canvas.getContent()).getChildren().clear();
                                        VFlow flow = (VFlow) rootFlow.getFlowById(n.getId());
                                        flow.addSkinFactories(new FXValueSkinFactory(null));
                                    }
                                }
                            }

                            if (c.wasRemoved()) {
                                for (VNode n : c.getRemoved()) {
                                    if (n.getId().equals(nodeId)) {
                                        stage.close();
                                    }
                                }
                            }
                        }
                    });
        }

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy