package ui.view.outliner; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.IOException; import javax.swing.AbstractCellEditor; import javax.swing.DropMode; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JTextField; import javax.swing.JTree; import javax.swing.SwingUtilities; import javax.swing.ToolTipManager; import javax.swing.TransferHandler; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.TreeCellEditor; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import classes.Holon; import classes.HolonElement; import classes.HolonObject; import ui.controller.Control; import util.ImageImport; /** * An Outliner Tab. * A high level view on the Model with respect to the Holon Structure. * @author Tom * */ public class HolonView extends JPanel{ private Control control; private JTree holonTree; private DefaultMutableTreeNode root; private boolean showHolonElementsInTree = true; public HolonView(Outliner parent, Control control) { this.control = control; this.setLayout(new BorderLayout()); initilizePanel(); parent.outlinerUpdate.addListener(() -> updateTreeStructure()); } public void initilizePanel() { holonTree = generateTree(); holonTree.setCellRenderer(new HolonJtreeRenderer()); holonTree.setDragEnabled(true); holonTree.setDropMode(DropMode.ON_OR_INSERT); holonTree.setTransferHandler(new HolonTransferHandler()); holonTree.getSelectionModel().setSelectionMode( TreeSelectionModel.SINGLE_TREE_SELECTION); ToolTipManager.sharedInstance().registerComponent(holonTree); TreeUtils.expand(holonTree); holonTree.setRootVisible(true); holonTree.addMouseListener(new HolonMouseListener()); holonTree.setEditable(true); holonTree.setCellEditor(new HolonNodeEditor()); this.add(TreeUtils.makePanelFromTree(holonTree)); } private JTree generateTree() { root = generateFromHolon(control.getModel().getStateHolon()); return new JTree(root); } private DefaultMutableTreeNode generateFromHolon(Holon holon) { DefaultMutableTreeNode node = new DefaultMutableTreeNode(new HolonInfo(holon)); for(Holon child : holon.getChildView()) { node.add(generateFromHolon(child)); } if(this.showHolonElementsInTree) { for(HolonElement ele : holon.getElementView()) { node.add(new DefaultMutableTreeNode(new ElementInfo(ele))); } } return node; } private void updateTreeStructure() { holonTree.clearSelection(); root.removeAllChildren(); Holon holon = control.getModel().getStateHolon(); for(Holon child : holon.getChildView()) { root.add(generateFromHolon(child)); } if(this.showHolonElementsInTree) { for(HolonElement ele : holon.getElementView()) { root.add(new DefaultMutableTreeNode(new ElementInfo(ele))); } } holonTree.updateUI(); TreeUtils.expand(holonTree); } interface HolonNode{ public Transferable getTransferable(HolonTransferHandler handler); public Icon getIcon(); public String getToolTip(); public void SetName(String name); } static final Icon elementIcon = new ImageIcon(ImageImport.loadImage("/Button_Images/element.png",16,16)); static final String elementToolTip = "Element"; class ElementInfo implements HolonNode{ private HolonElement element; ElementInfo(HolonElement element){ this.element = element; } @Override public String toString(){ return element.getName(); } public HolonElement getElement() { return element; } @Override public Transferable getTransferable(HolonTransferHandler handler) { return handler.new ElementTransferable(element); } @Override public Icon getIcon() { return elementIcon; } @Override public String getToolTip() { return elementToolTip; } @Override public void SetName(String name) { element.setEleName(name); } } static final Icon holonIcon = new ImageIcon(ImageImport.loadImage("/Button_Images/holon_logic.png",16,16)); static final Icon holonPhysicalIcon = new ImageIcon(ImageImport.loadImage("/Button_Images/holon_physical.png",16,16)); static final String holonToolTip = "Holon"; class HolonInfo implements HolonNode{ private Holon holon; HolonInfo(Holon holon){ this.holon = holon; } @Override public String toString(){ return holon.name; } public Holon getElement() { return holon; } public void AddElement(HolonElement element) { if(element.holon != null) { element.holon.removeElement(element); } holon.addElement(element); } public void AddHolon(Holon holon) { holon.split(this.holon, true); control.updateHolarchyWindow(); } @Override public Transferable getTransferable(HolonTransferHandler handler) { return handler.new HolonTransferable(holon); } @Override public Icon getIcon() { return holon.isPhysical? holonPhysicalIcon: holonIcon; } @Override public String getToolTip() { return holonToolTip; } @Override public void SetName(String name) { holon.name = name; } } //Context Menu class HolonMouseListener extends MouseAdapter{ JPopupMenu menu = new JPopupMenu (); JCheckBoxMenuItem showElements = new JCheckBoxMenuItem("Show Elements"); JMenuItem newHolonAsParentItem = new JMenuItem("Create new Holon as Parent"); JMenuItem newHolonAsChildItem = new JMenuItem("Create new Holon as Child"); JMenuItem deleteThisHolon = new JMenuItem("Delete this Holon"); public HolonMouseListener() { //Initialize MenuItems showElements.setState(showHolonElementsInTree); showElements.addActionListener(clicked -> { showHolonElementsInTree = showElements.getState(); updateTreeStructure(); }); newHolonAsParentItem.addActionListener(clicked -> { TreePath path = holonTree.getSelectionPath(); HolonInfo info = getInfoFromPath(path); if(info == null) return; Holon parent = info.holon.getParent(); parent.removeChildHolon(info.holon); Holon newHolon = new Holon("New Holon", control.getModel()); control.getModel().getHolonsByID().put(newHolon.getUniqueID(), newHolon); newHolon.merge(info.holon, true); parent.merge(newHolon, true); updateTreeStructure(); }); newHolonAsChildItem.addActionListener(clicked -> { TreePath path = holonTree.getSelectionPath(); HolonInfo info = getInfoFromPath(path); if(info == null) return; Holon newHolon = new Holon("New Holon", control.getModel()); info.holon.merge(newHolon, true); control.getModel().getHolonsByID().put(newHolon.getUniqueID(), newHolon); updateTreeStructure(); }); deleteThisHolon.addActionListener(clicked -> { TreePath path = holonTree.getSelectionPath(); HolonInfo info = getInfoFromPath(path); if(info == null) return; HolonObject obj = info.holon.getHolonObject(); control.getModel().getHolonsByID().replace(info.holon.getUniqueID(), info.holon); if(obj != null) { info.holon.reassignAllChildren(info.holon.getParent()); }else { info.holon.reassignAllChildren(info.holon.getParent()); info.holon.removeFromParent(); info.holon.removeAllRefrences(); } updateTreeStructure(); }); //CreateMenu menu.add(showElements); menu.addSeparator(); menu.add(newHolonAsParentItem); menu.add(newHolonAsChildItem); menu.add(deleteThisHolon); } public void mousePressed ( MouseEvent e ) { if (!SwingUtilities.isRightMouseButton(e)) { return; } TreePath path = holonTree.getSelectionPath(); boolean pathIsViable = (path != null); newHolonAsParentItem.setVisible(pathIsViable); newHolonAsChildItem.setVisible(pathIsViable); deleteThisHolon.setVisible(pathIsViable); menu.show(holonTree, e.getX(), e.getY()); } HolonInfo getInfoFromPath(TreePath path) { if(path == null) { return null; } DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent(); if(!(node.getUserObject() instanceof HolonInfo)) { return null; } return (HolonInfo)node.getUserObject(); } } //Drag and Drop //Make drag and drop von Holons possible. //Implement HolonElement drag and drop too, but its disabled. class HolonTransferHandler extends TransferHandler{ DataFlavor holonFlavor; DataFlavor[] holonFlavorArray = new DataFlavor[1]; DataFlavor elementFlavor; DataFlavor[] elementFlavorArray = new DataFlavor[1]; boolean allowHolonElementDragAndDrop = false; HolonTransferHandler(){ try { String mimeType = DataFlavor.javaJVMLocalObjectMimeType + ";class=\"" + classes.Holon.class.getName() + "\""; holonFlavor = new DataFlavor(mimeType); holonFlavorArray[0] = holonFlavor; mimeType = DataFlavor.javaJVMLocalObjectMimeType + ";class=\"" + classes.HolonElement.class.getName() + "\""; elementFlavor = new DataFlavor(mimeType); elementFlavorArray[0] = elementFlavor; } catch (ClassNotFoundException e) { e.printStackTrace(); } } @Override public boolean canImport(TransferHandler.TransferSupport support) { if(!support.isDrop()) { return false; } support.setShowDropLocation(true); //Only accept Element and Holon as Drop if(!support.isDataFlavorSupported(holonFlavor) && !support.isDataFlavorSupported(elementFlavor)) { return false; } JTree.DropLocation location = (JTree.DropLocation)support.getDropLocation(); TreePath pathDrop = location.getPath(); DefaultMutableTreeNode node = (DefaultMutableTreeNode) pathDrop.getLastPathComponent(); //Element cannot be parent of a holon if(!(node.getUserObject() instanceof HolonInfo)) { return false; } JTree tree = (JTree) support.getComponent(); TreePath pathDrag = tree.getSelectionPath(); //Dont allow Cycles if(pathDrag.isDescendant(pathDrop)) { return false; } //Check physical connected try { Holon dropHolon = ((HolonInfo) node.getUserObject()).holon; Holon dragHolon = (Holon) support.getTransferable().getTransferData(holonFlavor); boolean physicalConnected = control.getModel().checkHolonObjectsAreConnected(dragHolon, dropHolon, 0f); boolean isAllStateHolon = dropHolon.getParent() == null; return physicalConnected || isAllStateHolon; } catch (UnsupportedFlavorException | IOException e) { e.printStackTrace(); return false; } } @Override public boolean importData(TransferHandler.TransferSupport support) { if(!canImport(support)) { return false; } JTree.DropLocation location = (JTree.DropLocation)support.getDropLocation(); TreePath path = location.getPath(); DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent(); if(node.getUserObject() instanceof HolonInfo) { HolonInfo info = (HolonInfo) node.getUserObject(); try { if(support.isDataFlavorSupported(holonFlavor)) { info.AddHolon((Holon) support.getTransferable().getTransferData(holonFlavor)); }else if (support.isDataFlavorSupported(elementFlavor)){ info.AddElement((HolonElement) support.getTransferable().getTransferData(elementFlavor)); } } catch (UnsupportedFlavorException | IOException e) { e.printStackTrace(); } } return false; } protected void exportDone(JComponent source, Transferable data, int action) { updateTreeStructure(); } @Override public int getSourceActions(JComponent c) { return MOVE; } @Override protected Transferable createTransferable(JComponent c) { JTree tree = (JTree)c; TreePath path = tree.getSelectionPath(); if(path != null) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent(); Object userObject = node.getUserObject(); //Only allow Hollon to move if(this.allowHolonElementDragAndDrop) { //Both if(userObject instanceof HolonNode) { return ((HolonNode) userObject).getTransferable(this); } } else { //Only Holons if(userObject instanceof HolonInfo) { return ((HolonInfo) userObject).getTransferable(this); } } } return null; } public class HolonTransferable implements Transferable{ Holon holon; public HolonTransferable(Holon holon) { this.holon = holon; } @Override public DataFlavor[] getTransferDataFlavors() { return holonFlavorArray; } @Override public boolean isDataFlavorSupported(DataFlavor flavor) { return holonFlavor.equals(flavor); } @Override public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { if(!isDataFlavorSupported(flavor)) throw new UnsupportedFlavorException(flavor); return holon; } } public class ElementTransferable implements Transferable{ HolonElement element; public ElementTransferable(HolonElement element) { this.element = element; } @Override public DataFlavor[] getTransferDataFlavors() { return elementFlavorArray; } @Override public boolean isDataFlavorSupported(DataFlavor flavor) { return elementFlavor.equals(flavor); } @Override public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { if(!isDataFlavorSupported(flavor)) throw new UnsupportedFlavorException(flavor); return element; } } } //Cell Display class HolonJtreeRenderer extends DefaultTreeCellRenderer { public Component getTreeCellRendererComponent( JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row, boolean hasFocus) { super.getTreeCellRendererComponent(tree, value, isSelected, expanded, leaf, row, hasFocus); DefaultMutableTreeNode node = (DefaultMutableTreeNode) value; HolonNode Node = (HolonNode) node.getUserObject(); if(row != 0) { this.setIcon(Node.getIcon()); this.setToolTipText(Node.getToolTip()); } return this; } } //Edit Names of Nodes class HolonNodeEditor extends AbstractCellEditor implements TreeCellEditor{ private HolonNode holonNode; private JTextField tf= new JTextField(){ @Override public Dimension getPreferredSize() { Dimension dim = super.getPreferredSize(); int length = getText().length(); dim.width = Math.max(300, dim.width + length * 10) ; return dim; } }; public HolonNodeEditor() { super(); } @Override public Object getCellEditorValue() { holonNode.SetName(tf.getText()); return tf.getText(); } @Override public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) { JLabel newString = (JLabel) holonTree.getCellRenderer().getTreeCellRendererComponent(tree, value, isSelected, expanded, leaf, row, leaf); DefaultMutableTreeNode default_node = (DefaultMutableTreeNode) value; holonNode = (HolonNode)default_node.getUserObject(); tf.setText(newString.getText()); return tf; } } }