Java tutorial
/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package org.shaman.rpg.editor.objects.ui; import com.jme3.gde.core.assets.ProjectAssetManager; import java.awt.*; import java.awt.event.*; import java.awt.geom.AffineTransform; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.*; import javax.swing.border.LineBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.apache.commons.lang3.ArrayUtils; import org.netbeans.api.annotations.common.StaticResource; import org.netbeans.api.visual.action.*; import org.netbeans.api.visual.anchor.*; import org.netbeans.api.visual.graph.GraphPinScene; import org.netbeans.api.visual.layout.LayoutFactory; import org.netbeans.api.visual.widget.*; import org.netbeans.api.visual.widget.general.IconNodeWidget; import org.openide.awt.UndoRedo; import org.openide.explorer.propertysheet.PropertySheet; import org.openide.nodes.Node; import org.openide.util.Exceptions; import org.openide.util.ImageUtilities; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.shaman.database.IndexedRecord; import org.shaman.database.Record; import org.shaman.rpg.editor.objects.*; import org.shaman.rpg.editor.objects.undo.ElementEditUndoableEdit; import org.shaman.rpg.engine.core.math.Point2i; import org.shaman.rpg.engine.core.obj.*; import org.shaman.rpg.engine.core.obj.controller.AbstractController; import org.shaman.rpg.engine.core.obj.controller.AbstractMultiController; import org.shaman.rpg.engine.core.obj.model.AbstractModel; import org.shaman.rpg.engine.core.obj.model.AbstractMultiModel; public final class ElementPropertyVisualPanel1 extends JPanel implements ChangeListener { private static final Logger LOG = Logger.getLogger(ElementPropertyVisualPanel1.class.getName()); @StaticResource private static final String LOCK_ICON = "org/shaman/rpg/editor/objects/ui/lock24.png"; @StaticResource private static final String HIDDEN_ICON = "org/shaman/rpg/editor/objects/ui/hidden24.png"; private final PropertySheet propertySheet; private final GraphSceneImpl scene; private final ElementPropertyWizardPanel1 wizardPanel; private EditorLevelElement<BaseView, BaseModel<BaseView>, BaseController<BaseModel<BaseView>>> element; private ElementEditUndoableEdit edit; private MVCContainer container; private Collection<EditorLevelElement<?, ?, ?>> allElements; private ProjectAssetManager assetManager; private UndoRedo.Manager undoRedoManager; private ObjectPositions objectPositions; private ObjectNode selectedObject; { //force registering of property editors Installer.registerEditors(); } /** * Creates new form ElementPropertyVisualPanel1 */ public ElementPropertyVisualPanel1(ElementPropertyWizardPanel1 wizardPanel) { this.wizardPanel = wizardPanel; initComponents(); propertySheet = new PropertySheet(); scene = new GraphSceneImpl(); JScrollPane scenePane = new JScrollPane(scene.createView()); JButton addNew = new JButton( NbBundle.getMessage(ElementPropertyVisualPanel1.class, "ElementPropertyVisualPanel1.addNew")); JButton addExisting = new JButton( NbBundle.getMessage(ElementPropertyVisualPanel1.class, "ElementPropertyVisualPanel1.addExisting")); addNew.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { addNewObjectAction(); } }); addExisting.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { addExistingObjectAction(); } }); setLayout(new BorderLayout()); JPanel mainPanel = new JPanel(new GridLayout(1, 2, 5, 5)); mainPanel.add(scenePane); mainPanel.add(propertySheet); JPanel buttonRow = new JPanel(new FlowLayout(FlowLayout.LEFT)); buttonRow.add(addNew); buttonRow.add(addExisting); add(mainPanel, BorderLayout.CENTER); add(buttonRow, BorderLayout.SOUTH); } @SuppressWarnings("unchecked") void setElement(EditorLevelElement<?, ?, ?> element, MVCContainer container, Collection<EditorLevelElement<?, ?, ?>> allElements, ProjectAssetManager assetManager, UndoRedo.Manager undoRedoManager) { this.element = (EditorLevelElement<BaseView, BaseModel<BaseView>, BaseController<BaseModel<BaseView>>>) element; this.container = container; this.allElements = allElements; this.assetManager = assetManager; this.undoRedoManager = undoRedoManager; this.objectPositions = (ObjectPositions) element.get(ObjectPositions.KEY); if (this.objectPositions == null) { this.objectPositions = new ObjectPositions(); int i = 0; for (BaseView v : element.getViews()) { this.objectPositions.put(v, new Point2i(i * 50, 0)); i++; } i = 0; for (BaseModel m : element.getModels()) { this.objectPositions.put(m, new Point2i(i * 50, 100)); i++; } i = 0; for (BaseController c : element.getControllers()) { this.objectPositions.put(c, new Point2i(i * 50, 200)); i++; } element.put(ObjectPositions.KEY, this.objectPositions); } scene.setElement(); edit = new ElementEditUndoableEdit(allElements, container); edit.setOriginalElement(element); fireElementChanged(); } boolean isElementValid() { Collection<String> messages; //first check selected object if (selectedObject != null) { messages = ElementValidator.validate(selectedObject.getObject()); if (!messages.isEmpty()) { //set error message String msg = messages.iterator().next(); wizardPanel.setErrorMessage(selectedObject.getName() + ": " + msg); return false; } } //check all other elements for (ObjectInfo info : scene.getNodes()) { messages = ElementValidator.validate(info.node.getObject()); if (!messages.isEmpty()) { //set error message String msg = messages.iterator().next(); wizardPanel.setErrorMessage(info.node.getName() + ": " + msg); return false; } } //check additional validators for (LevelElementValidator<BaseView, BaseModel<BaseView>, BaseController<BaseModel<BaseView>>> v : element .getValidators()) { String message = v.validate(element); if (message != null) { wizardPanel.setErrorMessage(message); return false; } } return true; } void apply() { //Applies all changes, called when the OK-Button is pressed //notify object action providers for (ObjectInfo i : scene.getNodes()) { if (i.actionProvider != null) { try { i.actionProvider.onApprove(); } catch (Exception e) { LOG.log(Level.SEVERE, "error while executing onApprove() on action provider " + i.actionProvider, e); } } } //write positions (changes in the positions are not undoable) objectPositions.clear(); for (ObjectInfo i : scene.getNodes()) { Point p = i.widget.getLocation(); objectPositions.put((IndexedRecord) i.object, new Point2i(p.x, p.y)); } //Add undoable edit if (edit.setChangedElement(element)) { //add to UndoRedoManager undoRedoManager.addEdit(edit); } } void cancel() { //Cancles the changes (undo it), called when the Cancle-Button is pressed //notify object action providers for (ObjectInfo i : scene.getNodes()) { if (i.actionProvider != null) { i.actionProvider.onCancel(); } } //Undo edits if (edit.setChangedElement(element)) { edit.undo(); } } private void fireElementChanged() { System.out.println("fire element changed"); wizardPanel.onElementChanged(); } @Override public void stateChanged(ChangeEvent e) { fireElementChanged(); } public void addNewObjectAction() { Class<?> clazz = ChooseNewObject.show(null, element); if (clazz == null) { return; } //create an instance of the object Object obj = null; try { obj = clazz.newInstance(); } catch (InstantiationException | IllegalAccessException ex) { Exceptions.printStackTrace(ex); return; } addObject(obj, false); } private boolean addObject(Object obj, boolean hidden) { //check if it can be added int type; if (obj instanceof BaseView) { if (!hidden) { if (!element.canAdd(EditorLevelElement.ObjectType.VIEW, obj)) { return false; } BaseView[] views = Arrays.copyOf(element.getViews(), element.getViews().length + 1); views[views.length - 1] = (BaseView) obj; element.setViews(views); container.addView((BaseView) obj); } type = 1; } else if (obj instanceof BaseModel) { if (!hidden) { if (!element.canAdd(EditorLevelElement.ObjectType.MODEL, obj)) { return false; } BaseModel[] models = Arrays.copyOf(element.getModels(), element.getModels().length + 1); models[models.length - 1] = (BaseModel) obj; element.setModels(models); container.addModel((BaseModel) obj); } type = 2; } else if (obj instanceof BaseController) { if (!hidden) { if (!element.canAdd(EditorLevelElement.ObjectType.CONTROLLER, obj)) { return false; } BaseController[] controller = Arrays.copyOf(element.getControllers(), element.getControllers().length + 1); controller[controller.length - 1] = (BaseController) obj; element.setControllers(controller); container.addController((BaseController) obj); } type = 3; } else { throw new IllegalArgumentException("object is not a view, model or controller: " + obj); } //add it ObjectInfo info = new ObjectInfo(obj, type, hidden); scene.addObject(info); scene.repaint(); fireElementChanged(); return true; } public void addExistingObjectAction() { Object obj = ChooseExistingObject.show(null, element, allElements); if (obj != null) { addObject(obj, true); } } //<editor-fold defaultstate="collapsed" desc="Graph Element Structures"> private class ObjectInfo { private Object object; private ObjectNode node; private NodeWidget widget; private @Deprecated int type; //1=View, 2=Model, 3=Controller private EditorLevelElement.ObjectType typeEnum; private boolean hidden; private PinInfo inputPin; private ArrayList<PinInfo> outputPins; private ObjectNode.Links links; private ObjectActionProvider actionProvider; /** * Creates a new object info describing a view, model or controller * @param object the object * @param type 1: view, 2: model, 3: controller * @param hidden {@code true} if the object is hidden, meaning it is * refernced by a link from another higher-level object, but not * controller by the EditorLevelElement. */ private ObjectInfo(Object object, int type, boolean hidden) { this.object = object; this.type = type; if (type == 1) { typeEnum = EditorLevelElement.ObjectType.VIEW; } else if (type == 2) { typeEnum = EditorLevelElement.ObjectType.MODEL; } else { typeEnum = EditorLevelElement.ObjectType.CONTROLLER; } this.hidden = hidden; this.node = new ObjectNode(object, assetManager); //add pins inputPin = new PinInfo(this, true, 0); links = new ObjectNode.Links(object); outputPins = new ArrayList<>(); if (type != 1) { //views have no output pins int index = 0; for (int i = 0; i < links.getFixedSlotCount(); ++i) { PinInfo pin = new PinInfo(this, false, index++, links.getClassesAtSlot(i)); outputPins.add(pin); } //flexible pins, add minimum count for (int i = 0; i < Math.min(Math.max(links.getMinFlexible(), 1), links.getMaxFlexible()); ++i) { PinInfo pin = new PinInfo(this, false, index++, links.getFlexibleClasses()); outputPins.add(pin); } } //action provider for (ObjectActionProvider.Factory f : Lookup.getDefault() .lookupAll(ObjectActionProvider.Factory.class)) { try { ObjectActionProvider p = f.createActionProvider(element, object, typeEnum); if (p != null) { actionProvider = p; break; } } catch (Exception e) { LOG.log(Level.SEVERE, "error while initializing action provider", e); } } } /** * Requestes the specified count of pins. If the object contains a * flexible count of pins, this count is provided. * All outgoing edges and pins are removed from the scene. * @param count the count of available pins. */ private long requestPins(long count) { //remove old edges for (PinInfo pin : outputPins) { EdgeInfo edge = pin.edge; if (edge != null) { edge.source.edge = null; edge.target.edge = null; scene.removeEdge(edge); } scene.removePin(pin); } //minimal and maximal count of pins long minCount = links.getFixedSlotCount() + links.getMinFlexible(); long maxCount = links.getFixedSlotCount() + links.getMaxFlexible(); if (maxCount < 0) { maxCount = Integer.MAX_VALUE; //overflow because max flexible already Integer.MAX_VALUE } count = Math.max(minCount, count); count = Math.min(maxCount, count); //create pins outputPins.clear(); int index = 0; for (int i = 0; i < links.getFixedSlotCount(); ++i) { PinInfo pin = new PinInfo(this, false, index++, links.getClassesAtSlot(i)); outputPins.add(pin); } long flexibleCount = Math.min(links.getMaxFlexible(), count - links.getFixedSlotCount() + 1); //add one extra flexible slot for (int i = 0; i < flexibleCount; ++i) { PinInfo pin = new PinInfo(this, false, index++, links.getFlexibleClasses()); outputPins.add(pin); } //update slot names updateSlotNames(); //return count of created pins return count; } @Override public int hashCode() { int hash = 3; hash = 37 * hash + Objects.hashCode(this.object); hash = 37 * hash + this.type; return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final ObjectInfo other = (ObjectInfo) obj; if (!Objects.equals(this.object, other.object)) { return false; } if (this.type != other.type) { return false; } return true; } @Override public String toString() { return "ObjectInfo{" + "object=" + object + ", type=" + type + ", hidden=" + hidden + ", inputPin=" + inputPin + '}'; } private void updateSlotNames() { if (object instanceof NamedSlots) { NamedSlots ns = (NamedSlots) object; int slotCount; if (typeEnum == EditorLevelElement.ObjectType.CONTROLLER) { slotCount = ((BaseController) object).getModels().size(); } else if (typeEnum == EditorLevelElement.ObjectType.MODEL) { slotCount = ((BaseModel) object).getViews().size(); } else { LOG.severe("a view implements NamedSlots, this is not possible"); return; } String[] names; if (ns.getSlotNames() == null) { names = new String[slotCount]; } else { names = Arrays.copyOf(ns.getSlotNames(), slotCount); } if (slotCount > outputPins.size()) { return; //still in configuration } for (int i = 0; i < slotCount; ++i) { PinInfo pin = outputPins.get(i); if (pin.edge == null) { //names[i] = null; } else { String n = pin.edge.name; if (n == null || n.isEmpty()) { if (names[i] == null) { n = "null"; } else { n = names[i]; } pin.edge.name = n; scene.repaint(); } names[i] = n; } } ns.setSlotNames(names); } } } private class PinInfo { private ObjectInfo object; private boolean input; //true: input, false: output private int index; private Class<?>[] allowedClasses; private EdgeInfo edge; public PinInfo(ObjectInfo object, boolean input, int index, Class<?>... allowedClasses) { this.object = object; this.input = input; this.index = index; this.allowedClasses = allowedClasses; } @Override public int hashCode() { int hash = 7; hash = 97 * hash + Objects.hashCode(this.object); hash = 97 * hash + (this.input ? 1 : 0); hash = 97 * hash + this.index; return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final PinInfo other = (PinInfo) obj; if (!Objects.equals(this.object, other.object)) { return false; } if (this.input != other.input) { return false; } if (this.index != other.index) { return false; } return true; } private void connectTo(ObjectInfo target) { Object obj = this.object.object; if (obj instanceof AbstractModel) { @SuppressWarnings("unchecked") AbstractModel<BaseView> m = (AbstractModel<BaseView>) obj; assert (index == 0); m.setLinkedView((BaseView) target.object); } else if (obj instanceof AbstractMultiModel) { @SuppressWarnings("unchecked") AbstractMultiModel<BaseView> m = (AbstractMultiModel<BaseView>) obj; BaseView[] views = m.getLinkedViews(); if (views == null) { views = new BaseView[index + 1]; } if (views.length <= index) { views = Arrays.copyOf(views, index + 1); } views[index] = (BaseView) target.object; m.setLinkedViews(views); } else if (obj instanceof AbstractController) { @SuppressWarnings("unchecked") AbstractController<BaseModel<?>> c = (AbstractController<BaseModel<?>>) obj; assert (index == 0); c.setModel((BaseModel<?>) target.object); } else if (obj instanceof AbstractMultiController) { @SuppressWarnings("unchecked") AbstractMultiController<BaseModel<?>> c = (AbstractMultiController<BaseModel<?>>) obj; BaseModel[] models = c.getLinkedModels(); if (models == null) { models = new BaseModel[index + 1]; } if (models.length <= index) { models = Arrays.copyOf(models, index + 1); } models[index] = (BaseModel) target.object; c.setLinkedModels(models); } else { LOG.warning("unable to link the two objects " + obj.getClass() + " with " + target.object.getClass() + "\n, source is no AbstractModel, AbstractMultiModel, AbstractController or AbstractMultiController"); return; } if (obj instanceof NamedSlots) { NamedSlots ns = (NamedSlots) obj; String[] names = ns.getSlotNames(); names = Arrays.copyOf(names, Math.max(names.length, index + 1)); if (names[index] == null) { names[index] = "null"; } ns.setSlotNames(names); } LOG.info("successfully linked " + obj.getClass() + " with " + target.object.getClass() + " at pin " + index); } } private class EdgeInfo { private PinInfo source; private PinInfo target; private String name; public EdgeInfo(PinInfo source, PinInfo target) { this.source = source; this.target = target; source.edge = this; target.edge = this; } @Override public int hashCode() { int hash = 5; hash = 97 * hash + Objects.hashCode(this.source); hash = 97 * hash + Objects.hashCode(this.target); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final EdgeInfo other = (EdgeInfo) obj; if (!Objects.equals(this.source, other.source)) { return false; } if (!Objects.equals(this.target, other.target)) { return false; } return true; } public String getName() { return name; } public void setName(String name) { this.name = name; source.object.updateSlotNames(); } } //</editor-fold> private class SelectProviderImpl implements SelectProvider { private Widget oldSelected = null; @Override public boolean isAimingAllowed(Widget widget, Point localLocation, boolean invertSelection) { return true; } @Override public boolean isSelectionAllowed(Widget widget, Point localLocation, boolean invertSelection) { return true; } @Override public void select(Widget widget, Point localLocation, boolean invertSelection) { if (oldSelected == widget) { return; } if (oldSelected != null) { setSelected(oldSelected, false); } setSelected(widget, true); oldSelected = widget; LOG.info("widget selected: " + widget); } private void setSelected(Widget w, boolean selected) { if (w instanceof ConnectionWidget) { ((ConnectionWidget) w).setStroke(new BasicStroke(selected ? 3 : 1)); } else if (w instanceof PinWidget) { ((PinWidget) w).setSelected(selected); } else if (w instanceof NodeWidget) { ((NodeWidget) w).setSelected(selected); } } } private class ConnectProviderImpl implements ConnectProvider { private final GraphSceneImpl scene; private ConnectProviderImpl(GraphSceneImpl scene) { this.scene = scene; } @Override public boolean isSourceWidget(Widget sourceWidget) { return sourceWidget instanceof PinWidget; } @Override public ConnectorState isTargetWidget(Widget sourceWidget, Widget targetWidget) { if (!(sourceWidget instanceof PinWidget)) { return ConnectorState.REJECT_AND_STOP; } if (!(targetWidget instanceof PinWidget)) { return ConnectorState.REJECT; } PinWidget s = (PinWidget) sourceWidget; PinWidget t = (PinWidget) targetWidget; //first test: check that input/output flag is different if (s.info.input == t.info.input) { return ConnectorState.REJECT; } //second test: source pin must not contain connections PinWidget target = s.info.input ? s : t; PinWidget source = t.info.input ? s : t; for (EdgeInfo e : scene.getEdges()) { if (e.source == source.info) { return ConnectorState.REJECT_AND_STOP; //already a connection } } //third test: check that the layers are correct if (source.info.object.type - target.info.object.type != 1) { return ConnectorState.REJECT; } //forth test: valid target against allowed source classes Class<?> targetClass = target.info.object.object.getClass(); Class<?>[] allowedClasses = source.info.allowedClasses; boolean allowed = false; for (Class<?> ac : allowedClasses) { if (ac.isAssignableFrom(targetClass)) { allowed = true; break; } } if (!allowed) { return ConnectorState.REJECT; } //all tests passed return ConnectorState.ACCEPT; } @Override public boolean hasCustomTargetWidgetResolver(Scene scene) { return false; } @Override public Widget resolveTargetWidget(Scene scene, Point sceneLocation) { throw new UnsupportedOperationException("Not supported"); //not needed because hasCustomTargetWidgetResolver returns false } @Override public void createConnection(Widget sourceWidget, Widget targetWidget) { PinWidget s = (PinWidget) sourceWidget; PinWidget t = (PinWidget) targetWidget; PinWidget target = s.info.input ? s : t; PinWidget source = t.info.input ? s : t; //connect them in real int index = source.info.index; source.info.connectTo(target.info.object); //add edge scene.addEdge(new EdgeInfo(source.info, target.info)); //if allowed, add new pin if (index == source.info.object.outputPins.size() - 1) { //it was the last pin long maxCount = source.info.object.links.getFixedSlotCount() + source.info.object.links.getMaxFlexible(); if (maxCount > index + 1) { //add next flexible pin PinInfo pin = new PinInfo(source.info.object, false, index + 1, source.info.object.links.getFlexibleClasses()); source.info.object.outputPins.add(pin); scene.addPin(source.info.object, pin); } } fireElementChanged(); } } private class PinWidget extends Widget { private final PinInfo info; private boolean selected; private PinWidget(GraphSceneImpl scene, PinInfo info) { super(scene); this.info = info; this.setPreferredSize(new Dimension(20, 20)); WidgetAction.Chain actions = getActions(); actions.addAction(scene.createObjectHoverAction()); actions.addAction(scene.createSelectAction()); actions.addAction(ActionFactory.createConnectAction(scene.interactionLayer, scene.connectProvider)); actions.addAction(ActionFactory.createSelectAction(scene.selectProvider)); if (!info.input) { StringBuilder tt = new StringBuilder(); tt.append("<html>"); for (int i = 0; i < info.allowedClasses.length; ++i) { tt.append(info.allowedClasses[i].getSimpleName()); if (i < info.allowedClasses.length - 1) { tt.append("<br>"); } } tt.append("</html>"); setToolTipText(tt.toString()); } } @Override protected void paintWidget() { Graphics2D g = getGraphics(); int w = 20; int h = 20; g.setColor(Color.BLACK); int strength = selected ? 8 : 4; g.fillRect((w - strength) / 2, 0, strength, h); } private void setSelected(boolean selected) { this.selected = selected; repaint(); } } private class NodeWidget extends Widget { private final IconNodeWidget body; private final Widget upperPinContainer; private final Widget lowerPinContainer; private Color color; private NodeWidget(final GraphSceneImpl scene, final ObjectInfo node) { super(scene); node.widget = this; //create body widget body = new IconNodeWidget(scene); if (node.hidden) { body.setBackground(Color.GRAY); LOG.info(node + " is hidden"); } if (!element.canDelete(node.typeEnum, node.object)) { body.setImage(ImageUtilities.loadImage(LOCK_ICON)); } if (node.hidden) { body.setImage(ImageUtilities.loadImage(HIDDEN_ICON)); } body.setLabel(node.object.toString()); Point2i p = objectPositions.get((IndexedRecord) node.object); switch (node.type) { case 1: //VIEW color = Color.RED; if (node.hidden) color.brighter(); body.setBorder(new LineBorder(color)); if (p != null) { this.setPreferredLocation(new Point(p.x, p.y)); } else { this.setPreferredLocation(new Point((scene.viewCount++) * 50, 0)); } break; case 2: //MODEL color = Color.BLUE; if (node.hidden) color.brighter(); body.setBorder(new LineBorder(color)); if (p != null) { this.setPreferredLocation(new Point(p.x, p.y)); } else { this.setPreferredLocation(new Point((scene.viewCount++) * 50, 100)); } break; case 3: //CONTROLLER color = Color.GREEN; if (node.hidden) color.brighter(); body.setBorder(new LineBorder(color)); if (p != null) { this.setPreferredLocation(new Point(p.x, p.y)); } else { this.setPreferredLocation(new Point((scene.viewCount++) * 50, 200)); } break; } //actions WidgetAction.Chain actions = super.getActions(); actions.addAction(scene.createObjectHoverAction()); actions.addAction(scene.createSelectAction()); actions.addAction(ActionFactory.createMoveAction()); //TODO: add actions for popup actions and double-click actions, depending on the ObjectActionProvider // actions.addAction (ActionFactory.createSelectAction(scene.selectProvider)); InputMap inputMap = new InputMap(); ActionMap actionMap = new ActionMap(); String key = "delete"; actionMap.put(key, new AbstractAction("delete") { @Override public void actionPerformed(ActionEvent e) { if (scene.getFocusedWidget() == NodeWidget.this) { System.out.println("delete node"); scene.mainLayer.removeChild(NodeWidget.this); scene.removeNode(node); scene.setFocusedWidget(null); } } }); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), key); actions.addAction(ActionFactory.createActionMapAction(inputMap, actionMap)); //create upper pin container upperPinContainer = new Widget(scene); upperPinContainer.setLayout(LayoutFactory.createHorizontalFlowLayout()); //create lower pin container lowerPinContainer = new Widget(scene); lowerPinContainer.setLayout(LayoutFactory.createHorizontalFlowLayout()); //add them to this parent widget this.setLayout(LayoutFactory.createVerticalFlowLayout()); this.addChild(upperPinContainer); this.addChild(body); this.addChild(lowerPinContainer); //tooltip: implemented interfaces and superclasses LinkedHashSet<String> lines = new LinkedHashSet<>(); Class<?> clazz = node.object.getClass(); lines.add(clazz.getCanonicalName()); addClasses(clazz, lines); StringBuilder tt = new StringBuilder(); tt.append("<html>"); String[] lineArray = lines.toArray(new String[lines.size()]); for (int i = 0; i < lineArray.length; ++i) { tt.append(lineArray[i]); if (i < lineArray.length - 1) { tt.append("<br>"); } } tt.append("</html>"); body.setToolTipText(tt.toString()); } private void addClasses(Class<?> clazz, LinkedHashSet<String> lines) { if (clazz.getSuperclass() != null) { lines.add(clazz.getSuperclass().getSimpleName()); } for (Class<?> c : clazz.getInterfaces()) { lines.add(c.getSimpleName()); } for (Class<?> c : clazz.getInterfaces()) { addClasses(c, lines); } if (clazz.getSuperclass() != null) { addClasses(clazz.getSuperclass(), lines); } } private void addPin(PinWidget pin) { if (pin.info.input) { lowerPinContainer.addChild(pin); } else { upperPinContainer.addChild(pin); } } private void setSelected(boolean selected) { body.setBorder(new LineBorder(color, selected ? 3 : 1)); } } /** * Extends {@link ConnectionWidget} by adding a name to the link */ private class EdgeWidget extends ConnectionWidget { private final EdgeInfo info; public EdgeWidget(final Scene scene, final EdgeInfo info) { super(scene); this.info = info; assert (info != null); //this action reacts on double clicks and activates the name editing getActions().addAction(new WidgetAction.Adapter() { @Override public WidgetAction.State mouseClicked(Widget widget, WidgetAction.WidgetMouseEvent event) { if (event.getClickCount() == 2 && event.getButton() == MouseEvent.BUTTON1 && info.getName() != null) { //edit name String n = JOptionPane.showInputDialog(ElementPropertyVisualPanel1.this, "Set edge name", info.getName()); if (n != null && !n.isEmpty()) { info.setName(n); EdgeWidget.this.repaint(); } return WidgetAction.State.CONSUMED; } return super.mouseClicked(widget, event); } }); } @Override protected void paintWidget() { super.paintWidget(); if (info.getName() != null) { Graphics2D g = getGraphics(); AffineTransform trafo = new AffineTransform(g.getTransform()); Point ps = getSourceAnchor().getRelatedSceneLocation(); Point pt = getTargetAnchor().getRelatedSceneLocation(); trafo.translate((ps.x + pt.x) / 2, (ps.y + pt.y) / 2); trafo.rotate(pt.x - ps.x, pt.y - ps.y); if (Math.signum(pt.x - ps.x) + Math.signum(pt.y - ps.y) != 0) { //text stands on the head -> rotate around 180 trafo.rotate(Math.PI); } g.setColor(getForeground()); AffineTransform oldTrafo = g.getTransform(); g.setTransform(trafo); FontMetrics fm = g.getFontMetrics(); float sw = fm.stringWidth(info.getName()); float sh = fm.getDescent(); g.drawString(info.getName(), -sw / 2, -sh); g.setTransform(oldTrafo); } } } private class GraphSceneImpl extends GraphPinScene<ObjectInfo, EdgeInfo, PinInfo> { private LayerWidget mainLayer; private LayerWidget connectionLayer; private LayerWidget interactionLayer; private WidgetAction moveAction = ActionFactory.createMoveAction(); private final ConnectProviderImpl connectProvider; private final SelectProviderImpl selectProvider; //very simple layout private int viewCount = 0; private int modelCount = 0; private int controllerCount = 0; private GraphSceneImpl() { mainLayer = new LayerWidget(this); addChild(mainLayer); connectionLayer = new LayerWidget(this); addChild(connectionLayer); interactionLayer = new LayerWidget(this); addChild(interactionLayer); connectProvider = new ConnectProviderImpl(this); selectProvider = new SelectProviderImpl(); } /** * Adds a new object to the scene. * The pins are created according to the ObjectInfo * @param info */ private void addObject(ObjectInfo info) { info.node.addChangeListener(ElementPropertyVisualPanel1.this); addNode(info); //add pins for (PinInfo p : info.outputPins) { addPin(info, p); } if (info.type != 3) { //no controller PinInfo pin = new PinInfo(info, true, info.type); info.inputPin = pin; addPin(info, pin); } } /** * Connects the object with the specified targets. * The targets are instances of View[], Model[] or Controller[] as returned * by getViews(), getModels() or getControllers(). * Warning: It is not checked if the connection is allowed (no validation). * All previous edges are removed. * @param info the source info * @param targets the target info */ private void connectPins(ObjectInfo info, Collection<? extends Object> targets) { long count = info.requestPins(targets.size()); assert (count >= targets.size()) : "Unable to create enough pins for " + info; for (PinInfo p : info.outputPins) { addPin(info, p); } int index = 0; for (Object t : targets) { boolean found = false; for (ObjectInfo i : getNodes()) { if (i.object == t) { //reference equality //add edge addEdge(new EdgeInfo(info.outputPins.get(index), i.inputPin)); found = true; break; } } if (found == false) { LOG.warning("no target widget found for object " + t); } index++; } info.updateSlotNames(); } private void setElement() { LOG.log(Level.INFO, "element views: {0}", Arrays.toString(element.getViews())); LOG.log(Level.INFO, "element models: {0}", Arrays.toString(element.getModels())); LOG.log(Level.INFO, "element controllers: {0}", Arrays.toString(element.getControllers())); //collect hidden objects Set<BaseView> hiddenViews = new HashSet<>(); Set<BaseModel<? extends BaseView>> hiddenModels = new HashSet<>(); for (BaseModel<? extends BaseView> m : element.getModels()) { if (m != null) { hiddenViews.addAll(m.getViews()); } } for (BaseController<? extends BaseModel<? extends BaseView>> c : element.getControllers()) { if (c != null) { hiddenModels.addAll(c.getModels()); } } hiddenViews.removeAll(Arrays.asList(element.getViews())); hiddenModels.removeAll(Arrays.asList(element.getModels())); LOG.log(Level.INFO, "hidden views: {0}", hiddenViews); LOG.log(Level.INFO, "hidden models: {0}", hiddenModels); for (BaseView v : element.getViews()) { addObject(new ObjectInfo(v, 1, false)); } for (BaseView v : hiddenViews) { if (v == null) continue; addObject(new ObjectInfo(v, 1, true)); } for (BaseModel<? extends BaseView> m : element.getModels()) { ObjectInfo info = new ObjectInfo(m, 2, false); addObject(info); connectPins(info, m.getViews()); } for (BaseModel<? extends BaseView> m : hiddenModels) { if (m == null) continue; ObjectInfo info = new ObjectInfo(m, 2, true); addObject(info); connectPins(info, m.getViews()); } for (BaseController<? extends BaseModel<?>> c : element.getControllers()) { ObjectInfo info = new ObjectInfo(c, 3, false); addObject(info); connectPins(info, c.getModels()); } } @Override protected Widget attachNodeWidget(ObjectInfo node) { node.updateSlotNames(); NodeWidget widget = new NodeWidget(this, node); mainLayer.addChild(widget); return widget; } @Override protected Widget attachEdgeWidget(final EdgeInfo edge) { //final ConnectionWidget widget = new ConnectionWidget (this); final EdgeWidget widget = new EdgeWidget(this, edge); widget.setTargetAnchorShape(AnchorShape.TRIANGLE_FILLED); Widget sourceWidget = findWidget(edge.source); Widget targetWidget = findWidget(edge.target); widget.setSourceAnchor(AnchorFactory.createRectangularAnchor(sourceWidget)); widget.setTargetAnchor(AnchorFactory.createRectangularAnchor(targetWidget)); WidgetAction.Chain actions = widget.getActions(); InputMap inputMap = new InputMap(); ActionMap actionMap = new ActionMap(); String key = "delete"; actionMap.put(key, new AbstractAction("delete") { @Override public void actionPerformed(ActionEvent e) { if (getFocusedWidget() == widget) { System.out.println("delete connection"); widget.setSourceAnchor(null); widget.setTargetAnchor(null); connectionLayer.removeChild(widget); removeEdge(edge); setFocusedWidget(null); } } }); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), key); actions.addAction(ActionFactory.createActionMapAction(inputMap, actionMap)); actions.addAction(createObjectHoverAction()); actions.addAction(createSelectAction()); connectionLayer.addChild(widget); //force name update edge.setName(edge.getName()); return widget; } @Override protected Widget attachPinWidget(ObjectInfo node, PinInfo pin) { PinWidget widget = new PinWidget(scene, pin); NodeWidget objectWidget = (NodeWidget) findWidget(node); objectWidget.addPin(widget); return widget; } @Override protected void attachEdgeSourceAnchor(EdgeInfo edge, PinInfo oldSourcePin, PinInfo sourcePin) { ConnectionWidget edgeWidget = (ConnectionWidget) findWidget(edge); Widget sourcePinWidget = findWidget(sourcePin); Anchor sourceAnchor = AnchorFactory.createRectangularAnchor(sourcePinWidget); edgeWidget.setSourceAnchor(sourceAnchor); } @Override protected void attachEdgeTargetAnchor(EdgeInfo edge, PinInfo oldTargetPin, PinInfo targetPin) { ConnectionWidget edgeWidget = (ConnectionWidget) findWidget(edge); Widget targetPinWidget = findWidget(targetPin); Anchor targetAnchor = AnchorFactory.createRectangularAnchor(targetPinWidget); edgeWidget.setTargetAnchor(targetAnchor); } @Override public void userSelectionSuggested(Set<?> suggestedSelectedObjects, boolean invertSelection) { System.out.println("user selection: " + suggestedSelectedObjects); Object first = suggestedSelectedObjects.iterator().next(); if (first instanceof ObjectInfo) { ObjectNode node = ((ObjectInfo) first).node; node.setReadOnlyProperties(((ObjectInfo) first).hidden); propertySheet.setNodes(new Node[] { node }); selectedObject = ((ObjectInfo) first).node; } setFocusedObject(first); setFocusedWidget(findWidget(first)); fireElementChanged(); } } /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always * regenerated by the Form Editor. */ // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { setLayout(new java.awt.GridLayout(1, 2, 0, 5)); }// </editor-fold>//GEN-END:initComponents @Override public String getName() { return ""; } // Variables declaration - do not modify//GEN-BEGIN:variables // End of variables declaration//GEN-END:variables }