import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;

import javax.swing.border.EmptyBorder;

import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.border.TitledBorder;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.border.LineBorder;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.URL;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.imageio.ImageIO;
import javax.swing.border.*;
import javax.swing.event.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.table.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
import java.util.List;
import java.awt.*;

import java.awt.*;
import java.beans.DefaultPersistenceDelegate;
import java.beans.Encoder;
import java.beans.Statement;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;

import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;

// MVC   modelisation  visualization  command

public class jdialog04 extends JDialog 
{
	private static JTable table;
	private JTree tree;
///	private static DefaultTableModel xxxmodel;
	private boolean close_Jdialog = false;
	private TreeModel model;
	private List<TreeNode[]> expanded;
	private JButton btn_load_tree;
	private JButton btn_save_tree;
	private JButton btn_load_table;
	private JButton btn_save_table;
	
	private JButton btn_analyse;
	
	
	private File file = new File("TableModel.xml");	
	private DefaultTableModel tableModel; 
	private JFileChooser myJFileChooser = new JFileChooser(new File(".")); 
	
//	private Class_plan intermediaire;   
	
	private ArrayList<String> tree_node = new ArrayList<String>();
	
	private Class_plan intermediaire = new Class_plan();
	

//	private ArrayList<String> load_tree_datas = new ArrayList<String>();
	

	private WriteXML WRITE_XML; 
	private ReadXML READ_XML; 
		
	private ArrayList<String> tree_datas = new ArrayList<String>();
	
	private ArrayList<String> tmp_tree_datas = new ArrayList<String>();

	DefaultMutableTreeNode root;
	
	TreeModelPlanDev model_load_datas;
	boolean flag_load_datas = false;
	

	private String mouse_click = "";
	
	public jdialog04(JFrame f, boolean modal) 
	{
	       super(f, modal);
	
	       WRITE_XML = new WriteXML();
	       READ_XML = new ReadXML();
	       
	       // fermer la fenetre
	       addWindowListener(new java.awt.event.WindowAdapter() 
	       {
	           public void windowClosing(java.awt.event.WindowEvent e) 
	           {
	        	   dispose();
	           }
	       });
	       
	       expanded = new ArrayList<TreeNode[]>();
	      
	       init();
     
	}
  
	private void init()
	{
		  	setResizable( false );
			setBounds(100, 100, 926, 550);
			setTitle("Test definition version 4- debug ...");
			getContentPane().setLayout(null);
	     	
			ImageIcon icon = null;
			icon = createImageIcon("resources/settings.png", "");
			setIconImage( icon.getImage() );
			
			setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
			
			JScrollPane scrollPane_2 = new JScrollPane();
			scrollPane_2.setBounds(50, 39, 183, 194);
			getContentPane().add(scrollPane_2);
			
			
			model_load_datas = new TreeModelPlanDev();
			// on charge seulement le node principal
			model_load_datas.load(flag_load_datas, tree_datas, intermediaire);
			flag_load_datas = !flag_load_datas;
		    tree = new JTree(model_load_datas);
		
		    //root = new DefaultMutableTreeNode("Plan");

		
		    //tree = new JTree(root);
		    //tree.setRowHeight(45);
		    //tree = new JTree();
		
			tree.setComponentPopupMenu(new SelectionTreePopupMenu_bidule(intermediaire, mouse_click));
			
			scrollPane_2.setViewportView(tree);
			 
			tree.addMouseListener(new MouseAdapter() 
			{
			      public void mouseClicked(MouseEvent me) 
			      {
			    	  doMouseClicked(me);
			      }
		    });
			
			JScrollPane scrollPane = new JScrollPane();
			scrollPane.setBounds(294, 39, 532, 159);
			getContentPane().add(scrollPane);
			
			String[] columnNames =  {"Test", "Descrition" , "rien"};
	     
		  
		    Object[][] data = {	{ "", "", ""}  };
		    
			tableModel = new DefaultTableModel(data, columnNames)
			{
		        @Override
		        public boolean isCellEditable(int row, int column)
		        {
		            // make read only fields except column 0,13,14
		            return column == 1 || column == 2 ;
		        }
		    };
			
		    table = new JTable(tableModel);
		    table.setRowHeight(30);
		    table.setFont(new Font("Serif", Font.BOLD, 20));
		    
		    //table.setEnabled(false);

	    
		    table.addFocusListener(new FocusAdapter() 
		    {
		        @Override
		        public void focusLost(FocusEvent e) 
		        {
		            //if (e.getOppositeComponent().getParent() != table) 
		            //{
		               // loseCellFocus();
		            	System.out.println("Lost focus");
		            	
		            	
		            	String retour = table.getValueAt(0, 0).toString();
		            	
		            	//int row = table.getSelectedRow();
		            	String test_str = table.getModel().getValueAt(0, 0).toString();
		            	String descrition_str = table.getModel().getValueAt(0, 1).toString();
		            	String rien_str = table.getModel().getValueAt(0, 2).toString();
		            	
		            	System.out.println(test_str + " " + descrition_str + " " + rien_str );
		            	
		            	TestPlan obj = intermediaire.GetSequenceNamed(test_str);
		            	
		            	obj.setTextchamp1(descrition_str);
		            	obj.setTextchamp2(rien_str);
		            	
		            	//table.getCellEditor().stopCellEditing();
		            	
		            	/*if (table.isEditing())
		            	{	
		            	     table.getCellEditor().stopCellEditing();
		            	}   */  
		            	
		           // }
		        }
		    });
	
	    
		    scrollPane.setViewportView(table);

		    /*******************/
		
			btn_load_tree  = new JButton("Load");
			btn_load_tree.addActionListener(new ActionListener() 
			{
				public void actionPerformed(ActionEvent arg0) 
				{
					System.out.println("Btn load  \n");
					loadJtree();
				}
			});
			btn_load_tree.setBounds(50, 257, 89, 23);
			getContentPane().add(btn_load_tree);
			
		    /*******************/
				
			btn_save_tree = new JButton("Save");
			btn_save_tree.addActionListener(new ActionListener() 
			{
				public void actionPerformed(ActionEvent arg0) 
				{
					System.out.println("Btn save  \n");
			        TreeModel model = tree.getModel(); 
					saveJtree(model);
				}
			});
			btn_save_tree.setBounds(161, 257, 89, 23);
			getContentPane().add(btn_save_tree);
		
			/*******************/
		
			btn_load_table  = new JButton("Query");
			btn_load_table.addActionListener(new ActionListener() 
			{
				public void actionPerformed(ActionEvent arg0) 
				{
					System.out.println("Btn load table \n");
					
					TreeModel model = tree.getModel();
				    
					split02.myArea.append(" Tree extraction: " + "\n");
					
					ArrayList<String> explore_datas = explore_tree(model);
					for (int i = 0; i < explore_datas.size() ; i++) 
					{
						split02.myArea.append(" Node : " + explore_datas.get(i) + "\n");
						System.out.println(explore_datas.get(i));
					}
					
				    ArrayList<TestPlan>  objTree  = new ArrayList<TestPlan>();
				    objTree = intermediaire.getTreeChild();
	                split02.myArea.append(" Tree elements : " + objTree.size() + "\n");
				}
			});
			btn_load_table.setBounds(450, 257, 89, 23);
			getContentPane().add(btn_load_table);
		
			/*******************/
			
			JSeparator separator = new JSeparator();
			separator.setBounds(240, 302, 440, 2);
			getContentPane().add(separator);
		
			/*******************/
		
			JButton btn_ok = new JButton("OK");
			btn_ok.addActionListener(new ActionListener() 
			{
				public void actionPerformed(ActionEvent arg0) 
				{
					close_Jdialog = false;
					System.out.println("Btn Cancel  \n");
					dispose();
				}
			});
			btn_ok.setBounds(135, 391, 89, 23);
			getContentPane().add(btn_ok);
	
			/*******************/
			
			JButton btn_cancel = new JButton("Cancel");
			btn_cancel.addActionListener(new ActionListener() 
			{
				public void actionPerformed(ActionEvent e) 
				{
					close_Jdialog = false;
					System.out.println("Btn Cancel  \n");
					dispose();
				}
			});
			btn_cancel.setBounds(359, 391, 89, 23);
			getContentPane().add(btn_cancel);
		
	}
    
	public ImageIcon createImageIcon(String path,  String description) 
	{
		URL imgURL = getClass().getResource(path);

		if (imgURL != null)
		{
  			return new ImageIcon(imgURL, description);
		} 
		else 
		{
			System.err.println("Couldn't find file: " + path);
			return null;
		}
	}
   
    public  ArrayList<String> explore_tree(TreeModel model)
    {
    	 Object alpha = model.getRoot();
         String root_str = alpha.toString();
         System.out.println(root_str);
         
         ArrayList<String> array = new ArrayList<String>();
         
         for(int i = 0; i < ((DefaultMutableTreeNode) alpha).getChildCount() ; i++) 
         {
             DefaultMutableTreeNode child = (DefaultMutableTreeNode) ((DefaultMutableTreeNode) alpha).getChildAt(i);
             String noeud = child.getUserObject().toString();
             //System.out.println(noeud);
             array.add(noeud);
             
             for(int j = 0; j < child.getChildCount() ; j++) 
             {
                 DefaultMutableTreeNode aaachild = (DefaultMutableTreeNode) child.getChildAt(j);
                 String sub = aaachild.getUserObject().toString();
                 TestPlan obj = intermediaire.GetSequenceNamed(sub);
 				 String val1 = obj.getTextchamp1();
 	        	 String val2 = obj.getTextchamp2();
 	        	 //System.out.println(sub + "-" + val1 + "-" + val2);
 	        	 array.add(sub + "-" + val1 + "-" + val2);
             }
         }
         
         return(array);
    }
    
	public void doMouseClicked(MouseEvent me) 
	{
		TreePath TreePathObj = tree.getPathForLocation(me.getX(), me.getY());
		
		if(TreePathObj != null)
		{	
			Object pathString[] = TreePathObj.getPath();
		
			String value = "";
			String s = "";
			String str_concate = "";

			for (int i = 0; i < pathString.length ; i++) 
			{
				str_concate+=pathString[i] + ":";
			}	

			// nb d'ocuurence *
			int nb_occurence = str_concate.length() - str_concate.replace(":", "").length();
			
			System.out.println("----->  " + str_concate + " " + nb_occurence );  
			
			mouse_click = str_concate;
			
			if(nb_occurence == 3)
			{
				// enlever le dernier caractere du path 
				String str_tmp = str_concate.substring(0, str_concate.length()-1); 
				// decoupe
				String[] parts = str_tmp.split(":");
				
				System.out.println(" Selection children " + parts[2] );   
				tableModel.setValueAt(parts[2], 0, 0);
				
				//System.out.println(" Selection children " + parts[1]);   
				TestPlan obj = intermediaire.GetSequenceNamed(parts[2]);
				String val1 = obj.getTextchamp1();
	        	String val2 = obj.getTextchamp2();
				tableModel.setValueAt(val1, 0, 1);
				tableModel.setValueAt(val2, 0, 2);
				//table.setEnabled(true);
			}
			
			else
			{
				//int[] selected_row = table.getSelectedRows();
				//if( selected_row.length != 0 ) //&& nb_occurence == 3)
				//{	
				//	System.out.println("TRACE");
				//	table.getCellEditor().stopCellEditing();
				//}
				
				tableModel.setValueAt("", 0, 0);
				tableModel.setValueAt("", 0, 1);
				tableModel.setValueAt("", 0, 2);
				//table.setEnabled(false);
				
			}
		
		}
		else
		{
			System.out.println(" Jtree area ");   
		}
 	
	}
	
	
	private void loadJtree() 
	{
	    	 JFileChooser chooser = new JFileChooser();
	    	 chooser.setDialogTitle("Choice XML file");
	    	 FileNameExtensionFilter filter = new FileNameExtensionFilter( "XML file", "xml" );
	    	 chooser.setFileFilter(filter);
	    	 
	    	 int returnVal = chooser.showOpenDialog(this);
	
	    	 if(returnVal == JFileChooser.APPROVE_OPTION) 
	    	 {
	    	       File selectedFile = chooser.getSelectedFile();
	    	       tree_datas = READ_XML.Tree_ReadXML( selectedFile.getPath() );
	    	       model_load_datas.load(flag_load_datas, tree_datas, intermediaire);
				   flag_load_datas = !flag_load_datas;
	    	 }
	}
	   
    private void saveJtree(TreeModel model) 
	{
	    	JFileChooser fileChooser = new JFileChooser();
	    	fileChooser.setDialogTitle("Specify a file to save");   
	    	 
	    	// fileChooser.setSelectedFile(new File("myfile.xml"));
	    	fileChooser.setFileFilter(new FileNameExtensionFilter("xml file","xml"));
	    	
	    	int userSelection = fileChooser.showSaveDialog(this);
	    	 
	    	if (userSelection == JFileChooser.APPROVE_OPTION) 
	    	{
	    	    File fileToSave = fileChooser.getSelectedFile();
	    	    System.out.println("Save as file: " + fileToSave.getAbsolutePath());
	    	    
	    	    // collecte les datas
	    	    
	    	    tmp_tree_datas.clear();
	    	    tmp_tree_datas = explore_tree(model);
	    	    
	    	    /*ArrayList<TestPlan>  objTree  = new ArrayList<TestPlan>();
			    objTree = intermediaire.getTreeChild();

	            for(int i = 0; i < objTree.size(); i++)
	            {
	            	String type = objTree.get(i).getType();
	            	String sequence = objTree.get(i).getSequence();
					String val1 = objTree.get(i).getTextchamp1();
		        	String val2 = objTree.get(i).getTextchamp2();
		        	if(val1.equals("") && val2.equals("") )
		        	{
		        		tmp_tree_datas.add(sequence);
		        	}
		        	else
		        	{	
		        		tmp_tree_datas.add(sequence + "-" + val1 + "-" + val2);
		        	}
		        	System.out.println(" DATA collection to save in file: " + type + "-" + val1 + "-" + val2);
	            }*/
	    	    
	    	    WRITE_XML.Tree_WirteXML(tmp_tree_datas, fileToSave.getAbsolutePath());
	    	    
	    	}
	    	
	}
  
}

/****************************************************************************************/
class SelectionTreePopupMenu_bidule extends JPopupMenu 
{
	  protected TreePath path;

	  int NODE_MAXIMUM_LEVELS = 2;
	  
	 // Class_plan intermediaire = new Class_plan();

	  int level = 0;
	  int sublevel = 0;
	  
	  protected SelectionTreePopupMenu_bidule(Class_plan intermediaire, String mouse_click) 
	  {
	    super();

	     // intermediaire.addSequence(new TestPlan());
	    
	    add("add child node").addActionListener(e -> 
	    {
	    		JTree tree = (JTree) getInvoker();
	    		TreePath path = tree.getSelectionPath();
	    	
	    		if(path != null )
	    		{
	    			if (path.getPathCount() <= NODE_MAXIMUM_LEVELS) 
	    			{
			    		  DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
			    		  DefaultMutableTreeNode self = (DefaultMutableTreeNode) path.getLastPathComponent();
			    		  DefaultMutableTreeNode child = new DefaultMutableTreeNode("New child node");
			    		  self.add(child);
			    		  model.reload(self);
			  
			    		  Object pathString[] = path.getPath();
			    		  int n = pathString.length;
			    		  String type = "";
		    		  		    		  
			    		  switch(n) 
			    		  {
				             case 1 :
				            	 		type = "test";
				            	 		level = level + 1;
				            	 		System.out.println(" [Infos]:  level ---> " + type + " " + level);
				            	 		break;
				             case 2 :
				            	 		type = "subtest";
				            	 		sublevel = sublevel + 1;
				            	 		 System.out.println(" [Infos]:  ---> " + type + " " + sublevel);
				            	 		break;
				             default :
				            	 		System.out.println("Invalid choice");
			    		  }
			    			
			    		  intermediaire.addSequence(new TestPlan(type, level, "New child node", "", "") );
			    		  //System.out.println(" [Infos]:  ---> " + type + " " + level);
		    		  
		    	  }
		    	  else 
			      {
			    	  String message = String.format("ERROR: Maximum levels of %d exceeded.", NODE_MAXIMUM_LEVELS);
			          JOptionPane.showMessageDialog(tree, message, "add node", JOptionPane.ERROR_MESSAGE);
			      }
		    	  
		      }
		      else
		      {
		    	  String message = String.format("ERROR: Please , select tree node !. ");
		 	      JOptionPane.showMessageDialog(tree, message, "add child node", JOptionPane.ERROR_MESSAGE);
		      }
		      
	    });
	    
	    addSeparator();
	    
	    add("insert preceding sibling node").addActionListener(e -> 
	    {
	    	if( mouse_click.equals("Plan"))
	    	{
	    		JTree tree = (JTree) getInvoker();
	    		DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
	    		DefaultMutableTreeNode self = (DefaultMutableTreeNode) path.getLastPathComponent();
	    		DefaultMutableTreeNode parent = (DefaultMutableTreeNode) self.getParent();
	    		DefaultMutableTreeNode child = new DefaultMutableTreeNode("New preceding sibling");
	    		int index = model.getIndexOfChild(parent, self);
	    		parent.insert(child, index);
	    		model.reload(parent);
	    	}
	    	else
	    	{
		    	  String message = String.format("You can not add node at root level !.");
		          JOptionPane.showMessageDialog(this, message, "Warning", JOptionPane.ERROR_MESSAGE);

	    	}
	      // or: model.insertNodeInto(child, parent, index);
	    });
	    
	    add("insert following sibling node").addActionListener(e -> 
	    {
	    	if( mouse_click.equals("Plan"))
	    	{
			    JTree tree = (JTree) getInvoker();
			    DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
			    DefaultMutableTreeNode self = (DefaultMutableTreeNode) path.getLastPathComponent();
			    DefaultMutableTreeNode parent = (DefaultMutableTreeNode) self.getParent();
			    DefaultMutableTreeNode 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);
			}
	    	else
	    	{
		    	  String message = String.format("You can not add node at root level !.");
		          JOptionPane.showMessageDialog(this, message, "Warning", JOptionPane.ERROR_MESSAGE);
	    	}
	    });
	    
	    
	    add("Remove").addActionListener(e -> 
	    {
	    	  DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
		      if (!node.isRoot()) 
		      {
		        JTree tree = (JTree) getInvoker();
		        DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
		        model.removeNodeFromParent(node);
		      }
	    });
		    
	    add("Edit").addActionListener(e -> 
	    {
	    	JTextField textField = new JTextField(); //24
	    	Object node = path.getLastPathComponent();
		      
	    	DefaultMutableTreeNode leaf = (DefaultMutableTreeNode) node;
	    	textField.setText(leaf.getUserObject().toString());
	    	JTree tree = (JTree) getInvoker();
	        
	    	if( !textField.getText().equals("Plan"))
	    	{	
		    	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();
		              String old_sequence = leaf.getUserObject().toString();
		              model.valueForPathChanged(path, str);

		              // mise à jour de la database
		             
		              TestPlan obj = intermediaire.GetSequenceNamed(old_sequence);
		              obj.setSequence(str); 
		              
		            });
		    	}
	    	}
	    	 else 
		     {
		    	  String message = String.format("ERROR: Plan level cannot be changed !.", NODE_MAXIMUM_LEVELS);
		          JOptionPane.showMessageDialog(tree, message, "Edit node", JOptionPane.ERROR_MESSAGE);
		      }
	    });
	    
	  }

	  @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));
	    }
	  }

}

/****************************************************************************************/

//http://www.java2s.com/Tutorials/Java/Swing_How_to/JTree/Refresh_new_JTree_model.htm
class TreeModelPlanDev extends DefaultTreeModel 
{

	  DefaultMutableTreeNode root = null;  // declaration en debut de fichier

	  public TreeModelPlanDev() 
	  {
		  super(new DefaultMutableTreeNode("Plan"));
		  root = (DefaultMutableTreeNode) getRoot();
	  }

	  public void load(boolean flag_load, ArrayList<String> datas, Class_plan intermediaire ) 
	  {
		  root.removeAllChildren();
		  
		  if (flag_load)
		  {	
			  DefaultMutableTreeNode treeObj = null;
			
			  for(int i = 0 ; i < datas.size() ; i++)
			  {
				  boolean retval = datas.get(i).contains("-");
				  if(retval == false )
				  {
					  treeObj = new DefaultMutableTreeNode( datas.get(i) );
					  root.add(treeObj);
					  intermediaire.addSequence(new TestPlan("test", 1, datas.get(i), "", "") );
				  }
				  else
				  {
					  String[] parts = datas.get(i).split("-");
					  treeObj.add(new DefaultMutableTreeNode( parts[0] ));
					  intermediaire.addSequence(new TestPlan("subtest", 1, parts[0], parts[1], parts[2]) );
				  }
			  }
		
			  nodeStructureChanged(root);
		  }
	  }

	  public TreeNode getRootNode() 
	  {
		  return root;
	  }
}



////http://www.drdobbs.com/jvm/easy-dom-parsing-in-java/231002580

/// https://fr.wikibooks.org/wiki/Programmation_Java/XML

// java 2 by example book
// http://www.informit.com/articles/article.aspx?p=24142&seqNum=3

// http://www.java67.com/2018/02/3-books-to-learn-java-from-scratch-in.html

// thinking java
//https://sophia.javeriana.edu.co/~cbustaca/docencia/POO-2016-01/documentos/Thinking_in_Java_4th_edition.pdf

// https://www.thaicreate.com/java/java-gui-swing-awt.html

// https://ateraimemo.com/Swing/StringPaintedCellProgressBar.html   ++++++

// https://ateraimemo.com/Swing/SplashScreen.html

// https://ateraimemo.com/Swing/TriStateCheckBox.html

// https://ateraimemo.com/Swing/StringPaintedCellProgressBar.html

// https://ateraimemo.com/Swing/HandDragScrollableTable.html

// https://ateraimemo.com/Swing/EditableComboCellEditor.html

// https://ateraimemo.com/Swing/CheckBoxCellEditor.html  +++

// https://ateraimemo.com/Swing/ComboCellEditor.html

// https://ateraimemo.com/Swing/CheckedRowColor.html

// https://ateraimemo.com/Swing/CheckBoxesInTableCell.html

// https://ateraimemo.com/Swing/TableHeaderCheckBox.html