org.praxislive.ide.pxr.graph.scene.PraxisGraphScene Maven / Gradle / Ivy
The newest version!
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2024 Neil C Smith.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 3 for more details.
*
* You should have received a copy of the GNU General Public License version 3
* along with this work; if not, see http://www.gnu.org/licenses/
*
*
* Please visit https://www.praxislive.org if you need additional information or
* have any questions.
*
*
* This file incorporates code from Apache NetBeans Visual Library, covered by
* the following terms :
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.praxislive.ide.pxr.graph.scene;
import java.awt.Color;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.swing.UIManager;
import org.netbeans.api.visual.action.ActionFactory;
import org.netbeans.api.visual.action.ConnectProvider;
import org.netbeans.api.visual.action.PopupMenuProvider;
import org.netbeans.api.visual.action.WidgetAction;
import org.netbeans.api.visual.anchor.Anchor;
import org.netbeans.api.visual.border.BorderFactory;
import org.netbeans.api.visual.graph.GraphPinScene;
import org.netbeans.api.visual.layout.LayoutFactory;
import org.netbeans.api.visual.layout.SceneLayout;
import org.netbeans.api.visual.router.ConnectionWidgetCollisionsCollector;
import org.netbeans.api.visual.router.Router;
import org.netbeans.api.visual.router.RouterFactory;
import org.netbeans.api.visual.widget.ConnectionWidget;
import org.netbeans.api.visual.widget.EventProcessingType;
import org.netbeans.api.visual.widget.LayerWidget;
import org.netbeans.api.visual.widget.Widget;
/**
* A graph scene providing node widgets with pins and edges. The graph scene is
* backed by a set of nodes of the given type. A graph cannot contain duplicate
* nodes.
*
* @param node type
*/
public class PraxisGraphScene extends GraphPinScene, PinID> {
private final static double LOD_ZOOM = 0.7;
private final LayerWidget backgroundLayer = new LayerWidget(this);
private final LayerWidget mainLayer = new LayerWidget(this);
private final LayerWidget connectionLayer = new LayerWidget(this);
private final LayerWidget upperLayer = new LayerWidget(this);
private final CommentWidget commentWidget;
private boolean orthogonal;
private Router router;
private final WidgetAction moveAction;
private final PraxisKeyboardMoveAction keyboardMoveAction;
private final SceneLayout sceneLayout;
private LAFScheme scheme;
private WidgetAction menuAction;
private WidgetAction connectAction;
private LAFScheme.Colors schemeColors;
// private int edgeCount = 10;
/**
* Create a Praxis graph scene.
*/
public PraxisGraphScene() {
this(null, null, null);
}
/**
* Create a Praxis graph scene with a specific look and feel scheme.
*
* @param scheme the look and feel scheme
*/
public PraxisGraphScene(LAFScheme scheme) {
this(scheme, null, null);
}
/**
* Create a Praxis graph scene with the provided connect and popup menu
* providers.
*
* @param connectProvider connect provider
* @param popupProvider popup menu provider
*/
public PraxisGraphScene(ConnectProvider connectProvider, PopupMenuProvider popupProvider) {
this(null, connectProvider, popupProvider);
}
/**
* Create a Praxis graph scene with a specific look and feel scheme, and the
* provided connect and popup menu providers.
*
* @param scheme the look and feel scheme
* @param connectProvider connect provider
* @param popupProvider popup menu provider
*/
public PraxisGraphScene(LAFScheme scheme, ConnectProvider connectProvider, PopupMenuProvider popupProvider) {
if (scheme == null) {
scheme = new LAFScheme();
}
this.scheme = scheme;
setFont(UIManager.getFont("controlFont"));
setKeyEventProcessingType(EventProcessingType.FOCUSED_WIDGET_AND_ITS_PARENTS);
addChild(backgroundLayer);
addChild(mainLayer);
addChild(connectionLayer);
addChild(upperLayer);
PraxisMoveProvider mover = new PraxisMoveProvider(this, backgroundLayer);
moveAction = ActionFactory.createMoveAction(mover, mover);
keyboardMoveAction = new PraxisKeyboardMoveAction(mover, mover);
commentWidget = new CommentWidget(this);
commentWidget.setPreferredLocation(new Point(32, 32));
commentWidget.setBorder(BorderFactory.createRoundedBorder(8, 8, 8, 8, Color.LIGHT_GRAY, null));
commentWidget.setVisible(false);
mainLayer.addChild(commentWidget);
setBackground(scheme.getBackgroundColor());
router = RouterFactory.createDirectRouter();
getActions().addAction(ActionFactory.createWheelPanAction());
getActions().addAction(ActionFactory.createMouseCenteredZoomAction(1.2));
getActions().addAction(ActionFactory.createPanAction());
getActions().addAction(ActionFactory.createCycleFocusAction(new PraxisCycleFocusProvider()));
if (connectProvider != null) {
connectAction = ActionFactory.createConnectAction(new PraxisConnectDecorator(), connectionLayer, connectProvider);
}
if (popupProvider != null) {
menuAction = ActionFactory.createPopupMenuAction(popupProvider);
getActions().addAction(menuAction);
}
getActions().addAction(ActionFactory.createRectangularSelectAction(this, backgroundLayer));
addSceneListener(new ZoomCorrector());
sceneLayout = LayoutFactory.createSceneGraphLayout(this, new PraxisGraphLayout<>(true));
}
/**
* Add a node with the given name. Returns the node widget for further
* customization.
*
* @param node node
* @param name name of node
* @return node widget representation
*/
public NodeWidget addNode(N node, String name) {
NodeWidget n = (NodeWidget) super.addNode(node);
n.setNodeName(name);
return n;
}
@Override
protected void detachNodeWidget(N node, Widget widget) {
((NodeWidget) widget).getCommentWidget().removeFromParent();
super.detachNodeWidget(node, widget);
}
/**
* Add a pin with the given name to a node. The pin will be centrally
* aligned. Returns the pin widget attached to the node widget for further
* customization.
*
* @param node node to add pin to
* @param name name of pin
* @return pin widget representation
*/
public PinWidget addPin(N node, String name) {
return addPin(new PinID<>(node, name),
Alignment.Center);
}
/**
* Add a pin with the given name and alignment to a node. Returns the pin
* widget attached to the node widget for further customization.
*
* @param node node to add pin to
* @param name name of pin
* @param alignment pin alignment
* @return pin widget representation
*/
public PinWidget addPin(N node, String name, Alignment alignment) {
return addPin(new PinID<>(node, name), alignment);
}
/**
* Add a pin with the given ID and alignment. Returns the pin widget for
* further customization.
*
* @param pin pin ID
* @param alignment pin alignment
* @return pin widget representation
*/
public PinWidget addPin(PinID pin, Alignment alignment) {
if (pin == null || alignment == null) {
throw new NullPointerException();
}
PinWidget p = (PinWidget) super.addPin(pin.getParent(), pin);
p.setAlignment(alignment);
return p;
}
/**
* Add a connection between the given pins. Returns the edge widget for
* further customization.
*
* @param node1 first node
* @param pin1 first pin name
* @param node2 second node
* @param pin2 second pin name
* @return edge widget representation
*/
public EdgeWidget connect(N node1, String pin1, N node2, String pin2) {
return connect(new PinID<>(node1, pin1),
new PinID<>(node2, pin2));
}
/**
* Add a connection between the given pins. Returns the edge widget for
* further customization.
*
* @param p1 first pin ID
* @param p2 second pin ID
* @return edge widget representation
*/
public EdgeWidget connect(PinID p1, PinID p2) {
EdgeID d = new EdgeID<>(p1, p2);
EdgeWidget e = (EdgeWidget) addEdge(d);
setEdgeSource(d, p1);
setEdgeTarget(d, p2);
return e;
}
/**
* Disconnect the given pins.
*
* @param node1 first node
* @param pin1 first pin name
* @param node2 second node
* @param pin2 second pin name
*/
public void disconnect(N node1, String pin1, N node2, String pin2) {
PinID p1 = new PinID<>(node1, pin1);
PinID p2 = new PinID<>(node2, pin2);
EdgeID d = new EdgeID<>(p1, p2);
removeEdge(d);
}
/**
* Get the look and feel scheme.
*
* @return LAF scheme
*/
public LAFScheme getLookAndFeel() {
return scheme;
}
/**
* Set the scheme colours.
*
* @param schemeColors scheme colours
*/
public void setSchemeColors(LAFScheme.Colors schemeColors) {
this.schemeColors = schemeColors;
revalidate();
}
/**
* Get the scheme colours.
*
* @return scheme colours
*/
public LAFScheme.Colors getSchemeColors() {
return schemeColors;
}
/**
* Set whether to use orthogonal routing (as opposed to curved edges).
*
* @param orthogonal use orthogonal routing
*/
public void setOrthogonalRouting(boolean orthogonal) {
if (this.orthogonal != orthogonal) {
this.orthogonal = orthogonal;
setRouter(orthogonal
? RouterFactory.createOrthogonalSearchRouter(mainLayer, upperLayer)
: RouterFactory.createDirectRouter());
}
}
/**
* Query whether the graph is using orthogonal routing.
*
* @return using orthogonal routing
*/
public boolean isOrthogonalRouting() {
return orthogonal;
}
void setRouter(Router router) {
this.router = router;
for (EdgeID e : getEdges()) {
((ConnectionWidget) findWidget(e)).setRouter(router);
}
revalidate();
}
Router getRouter() {
return router;
}
@Override
public void userSelectionSuggested(Set> suggestedSelectedObjects, boolean invertSelection) {
if (suggestedSelectedObjects.size() == 1 && isPin(suggestedSelectedObjects.iterator().next())) {
suggestedSelectedObjects = Collections.emptySet();
} else if (!suggestedSelectedObjects.isEmpty()) {
Set