
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Objects;
import java.util.Optional;

import javax.swing.*;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import javax.swing.border.EmptyBorder;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;

public final class MainPanel extends JPanel 
{
  private MainPanel() 
  {
    super(new BorderLayout());
  
    JTree tree = new JTree() 
    {
      @Override public void updateUI() 
      {
        setCellRenderer(null);
        setCellEditor(null);
        //setEditable(true);
        super.updateUI();
        // ???#1: JDK 1.6.0 bug??? Nimbus LnF
        setCellRenderer(new CheckBoxNodeRenderer());
        setCellEditor(new CheckBoxNodeEditor());
      
        
        

      }
      
  
     
      
    };
    
    tree.setComponentPopupMenu(new TreePopupMenu());
    
    
    TreeModel model = tree.getModel();
    DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
    
    // Java 9: Collections.list(root.breadthFirstEnumeration()).stream()
    Collections.list((Enumeration<?>) root.breadthFirstEnumeration()).stream()
      .filter(DefaultMutableTreeNode.class::isInstance)
      .map(DefaultMutableTreeNode.class::cast)
      .forEach(n -> n.setUserObject(new CheckBoxNode(Objects.toString(n.getUserObject(), ""), Status.DESELECTED)));

    model.addTreeModelListener(new CheckBoxStatusUpdateListener());

    tree.setEditable(true);
    tree.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));

    tree.expandRow(0);
    // tree.setToggleClickCount(1);

    setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
    add(new JScrollPane(tree));
    setPreferredSize(new Dimension(320, 240));
    
    
    tree.addTreeSelectionListener(new SelectionListener());
  
    
  }

  
  class SelectionListener implements TreeSelectionListener {

	  public void valueChanged(TreeSelectionEvent se) 
	  {
	    JTree tree = (JTree) se.getSource();
	    //DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
	    //String selectedNodeName = selectedNode.toString();
	    
	    DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
	    
	    if (node == null)
	        // Nothing is selected.
	        return;

	       Object nodeInfo = node.getUserObject();
	    
	    //if (selectedNode.isLeaf()) 
	    if (node.isLeaf()) 	    	
	    {
	        System.out.println("THE ROOT NODE IS: "+node.getParent().toString()+" CHILD NODE IS: "+nodeInfo.toString());

	       //System.out.println(selectedNodeName);

	    }
	  }
	}
  
  public static void main(String... args) 
  {
	  EventQueue.invokeLater(new Runnable() 
	  {
		  @Override 
		  public void run() 
		  {
			  createAndShowGui();
		  }
	  });
  }

	  public static void createAndShowGui() 
	  {
	    try {
	      UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
	    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
	      ex.printStackTrace();
	    }
	    JFrame frame = new JFrame("@title@");
	    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
	    frame.getContentPane().add(new MainPanel());
	    frame.pack();
	    frame.setLocationRelativeTo(null);
	    frame.setVisible(true);
	  }
}

		class TriStateCheckBox extends JCheckBox 
		{
			@Override
			public void updateUI()
			{
				Icon currentIcon = getIcon();
				setIcon(null);
				super.updateUI();
				if (Objects.nonNull(currentIcon)) 
				{
					setIcon(new IndeterminateIcon());
				}
				setOpaque(false);
		}
}

		class IndeterminateIcon implements Icon 
		{
		  	private static final Color FOREGROUND = new Color(50, 20, 255, 200); // TEST: UIManager.getColor("CheckBox.foreground");
		  	private static final int SIDE_MARGIN = 4;
		  	private static final int HEIGHT = 2;
		  	private final Icon icon = UIManager.getIcon("CheckBox.icon");
	
		  	@Override 
		  	public void paintIcon(Component c, Graphics g, int x, int y) {
			Graphics2D g2 = (Graphics2D) g.create();
		  	g2.translate(x, y);
		  	icon.paintIcon(c, g2, 0, 0);
		  	g2.setPaint(FOREGROUND);
		  	g2.fillRect(SIDE_MARGIN, (getIconHeight() - HEIGHT) / 2, getIconWidth() - SIDE_MARGIN - SIDE_MARGIN, HEIGHT);
		  	g2.dispose();
		}

		@Override 
		public int getIconWidth() 
		{
			return icon.getIconWidth();
		}

		@Override
		public int getIconHeight() 
		{
		 	return icon.getIconHeight();
		}
}

		
//		
//		class TreePopupMenu extends JPopupMenu {
//			  protected TreePath path;
//			  private final Action addNodeAction = new AbstractAction("add") {
//			    @Override public void actionPerformed(ActionEvent e) {
//			      JTree tree = (JTree) getInvoker();
//			      DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
//			      DefaultMutableTreeNode parent = (DefaultMutableTreeNode) path.getLastPathComponent();
//			      DefaultMutableTreeNode child = new DefaultMutableTreeNode("New node");
//			      model.insertNodeInto(child, parent, parent.getChildCount());
//			      tree.scrollPathToVisible(new TreePath(child.getPath())); // https://ateraimemo.com/Swing/ScrollRectToVisible.html
//			    }
//			  };
//			  private final Action addReloadAction = new AbstractAction("add & reload") {
//			    @Override public void actionPerformed(ActionEvent e) {
//			      JTree tree = (JTree) getInvoker();
//			      DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
//			      DefaultMutableTreeNode parent = (DefaultMutableTreeNode) path.getLastPathComponent();
//			      DefaultMutableTreeNode child = new DefaultMutableTreeNode("New node");
//			      parent.add(child);
//			      model.reload(parent); // = model.nodeStructureChanged(parent);
//			      tree.scrollPathToVisible(new TreePath(child.getPath()));
//			    }
//			  };
//			  private final Action editNodeAction = new AbstractAction("edit") {
//			    protected final JTextField textField = new JTextField(24) {
//			      protected transient AncestorListener listener;
//			      @Override public void updateUI() {
//			        removeAncestorListener(listener);
//			        super.updateUI();
//			        listener = new AncestorListener() {
//			          @Override public void ancestorAdded(AncestorEvent e) {
//			            requestFocusInWindow();
//			          }
//
//			          @Override public void ancestorMoved(AncestorEvent e) { /* not needed */ }
//
//			          @Override public void ancestorRemoved(AncestorEvent e) { /* not needed */ }
//			        };
//			        addAncestorListener(listener);
//			      }
//			    };
//			    @Override public void actionPerformed(ActionEvent e) {
//			      Object node = path.getLastPathComponent();
//			      if (!(node instanceof DefaultMutableTreeNode)) {
//			        return;
//			      }
//			      DefaultMutableTreeNode leaf = (DefaultMutableTreeNode) node;
//			      textField.setText(leaf.getUserObject().toString());
//			      JTree tree = (JTree) getInvoker();
//			      int ret = JOptionPane.showConfirmDialog(tree, textField, "edit", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
//			      if (ret == JOptionPane.OK_OPTION) {
//			        Optional.ofNullable(textField.getText())
//			            .filter(str -> !str.trim().isEmpty())
//			            .ifPresent(str -> {
//			              DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
//			              model.valueForPathChanged(path, str);
//			              // leaf.setUserObject(str);
//			              // model.nodeChanged(leaf);
//			            });
//			      }
//			    }
//			  };
//			  private final Action removeNodeAction = new AbstractAction("remove") {
//			    @Override public void actionPerformed(ActionEvent e) {
//			      DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
//			      if (!node.isRoot()) {
//			        JTree tree = (JTree) getInvoker();
//			        DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
//			        model.removeNodeFromParent(node);
//			      }
//			    }
//			  };
//
//			  protected TreePopupMenu() {
//			    super();
//			    add(addNodeAction);
//			    add(addReloadAction);
//			    add(editNodeAction);
//			    addSeparator();
//			    add(removeNodeAction);
//			  }
//
//			  @Override public void show(Component c, int x, int y) {
//			    if (c instanceof JTree) {
//			      JTree tree = (JTree) c;
//			      // TreePath[] tsp = tree.getSelectionPaths();
//			      path = tree.getPathForLocation(x, y);
//			      // if (Objects.nonNull(path) && Arrays.asList(tsp).contains(path)) {
//			      Optional.ofNullable(path).ifPresent(p -> {
//			        tree.setSelectionPath(p);
//			        super.show(c, x, y);
//			      });
//			    }
//			  }
//			}
//		
		
		
		
	/**************************************************************************/	
		
		
//		class TreePopupMenu extends JPopupMenu 
//		{
//			  protected TreePath path;
//
//			  protected TreePopupMenu() 
//			  {
//			    super();
//			    add("add child node").addActionListener(e -> 
//			    {
//			      JTree tree = (JTree) getInvoker();
//			      DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
//			      DefaultMutableTreeNode self = (DefaultMutableTreeNode) path.getLastPathComponent();
//			      DefaultMutableTreeNode child = new DefaultMutableTreeNode("New child node");
//			      self.add(child);
//			      model.reload(self);
//			      // or: model.insertNodeInto(child, self, self.getChildCount());
//			    });
//			    
//			    addSeparator();
//			    
//			    add("insert preceding sibling node").addActionListener(e -> {
//			      JTree tree = (JTree) getInvoker();
//			      DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
//			      MutableTreeNode self = (MutableTreeNode) path.getLastPathComponent();
//			      MutableTreeNode parent = (MutableTreeNode) self.getParent();
//			      MutableTreeNode child = new DefaultMutableTreeNode("New preceding sibling");
//			      int index = model.getIndexOfChild(parent, self);
//			      parent.insert(child, index);
//			      model.reload(parent);
//			      // or: model.insertNodeInto(child, parent, index);
//			    });
//			    
//			    add("insert following sibling node").addActionListener(e -> {
//			      JTree tree = (JTree) getInvoker();
//			      DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
//			      MutableTreeNode self = (MutableTreeNode) path.getLastPathComponent();
//			      MutableTreeNode parent = (MutableTreeNode) self.getParent();
//			      MutableTreeNode child = new DefaultMutableTreeNode("New following sibling");
//			      int index = model.getIndexOfChild(parent, self);
//			      parent.insert(child, index + 1);
//			      model.reload(parent);
//			      // or: model.insertNodeInto(child, parent, index + 1);
//			    });
//			    
//			    addSeparator();
//			    
//			  
//			    
//				  add("Remove").addActionListener(e -> {
//				      JTree tree = (JTree) getInvoker();
//				      DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
//				      DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
//				      model.removeNodeFromParent(node);
//				      //model.reload(parent);
//				      // or: model.insertNodeInto(child, parent, index + 1);
//				    }); 
//				  
//				  
//				
//				  
//				  
//			  }
//
//			  @Override public void show(Component c, int x, int y) {
//			    if (c instanceof JTree) {
//			      JTree tree = (JTree) c;
//			      path = tree.getPathForLocation(x, y);
//			      Optional.ofNullable(path).ifPresent(tp -> super.show(c, x, y));
//			    }
//			  }
//			}
//		
//		
		
		/**************************************************************/
		
		class TreePopupMenu extends JPopupMenu {
			  protected TreePath path;
			  private final Action addNodeAction = new AbstractAction("add") {
			    @Override public void actionPerformed(ActionEvent e) {
			      JTree tree = (JTree) getInvoker();
			      DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
			      DefaultMutableTreeNode parent = (DefaultMutableTreeNode) path.getLastPathComponent();
			      DefaultMutableTreeNode child = new DefaultMutableTreeNode("New node");
			      model.insertNodeInto(child, parent, parent.getChildCount());
			      tree.scrollPathToVisible(new TreePath(child.getPath())); // https://ateraimemo.com/Swing/ScrollRectToVisible.html
			    }
			  };
			  private final Action addReloadAction = new AbstractAction("add & reload") {
			    @Override public void actionPerformed(ActionEvent e) {
			      JTree tree = (JTree) getInvoker();
			      DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
			      DefaultMutableTreeNode parent = (DefaultMutableTreeNode) path.getLastPathComponent();
			      DefaultMutableTreeNode child = new DefaultMutableTreeNode("New node");
			      parent.add(child);
			      model.reload(parent); // = model.nodeStructureChanged(parent);
			      tree.scrollPathToVisible(new TreePath(child.getPath()));
			    }
			  };
			  private final Action editNodeAction = new AbstractAction("edit") {
			    protected final JTextField textField = new JTextField(24) {
			      protected transient AncestorListener listener;
			      @Override public void updateUI() {
			        removeAncestorListener(listener);
			        super.updateUI();
			        listener = new AncestorListener() {
			          @Override public void ancestorAdded(AncestorEvent e) {
			            requestFocusInWindow();
			          }

			          @Override public void ancestorMoved(AncestorEvent e) { /* not needed */ }

			          @Override public void ancestorRemoved(AncestorEvent e) { /* not needed */ }
			        };
			        addAncestorListener(listener);
			      }
			    };
			    @Override public void actionPerformed(ActionEvent e) {
			      Object node = path.getLastPathComponent();
			      if (!(node instanceof DefaultMutableTreeNode)) {
			        return;
			      }
			      DefaultMutableTreeNode leaf = (DefaultMutableTreeNode) node;
			      textField.setText(leaf.getUserObject().toString());
			      JTree tree = (JTree) getInvoker();
			      int ret = JOptionPane.showConfirmDialog(tree, textField, "edit", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
			      if (ret == JOptionPane.OK_OPTION) {
			        Optional.ofNullable(textField.getText())
			            .filter(str -> !str.trim().isEmpty())
			            .ifPresent(str -> {
			              DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
			              model.valueForPathChanged(path, str);
			              // leaf.setUserObject(str);
			              // model.nodeChanged(leaf);
			            });
			      }
			    }
			  };
			  private final Action removeNodeAction = new AbstractAction("remove") {
			    @Override public void actionPerformed(ActionEvent e) {
			      DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
			      if (!node.isRoot()) {
			        JTree tree = (JTree) getInvoker();
			        DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
			        model.removeNodeFromParent(node);
			      }
			    }
			  };

			  protected TreePopupMenu() {
			    super();
			    add(addNodeAction);
			    add(addReloadAction);
			    add(editNodeAction);
			    addSeparator();
			    add(removeNodeAction);
			  }

			  @Override public void show(Component c, int x, int y) {
			    if (c instanceof JTree) {
			      JTree tree = (JTree) c;
			      // TreePath[] tsp = tree.getSelectionPaths();
			      path = tree.getPathForLocation(x, y);
			      // if (Objects.nonNull(path) && Arrays.asList(tsp).contains(path)) {
			      Optional.ofNullable(path).ifPresent(p -> {
			        tree.setSelectionPath(p);
			        super.show(c, x, y);
			      });
			    }
			  }
			}
		