All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tools.vitruv.change.testutils.changevisualization.tree.ChangeTree Maven / Gradle / Ivy

The newest version!
package tools.vitruv.change.testutils.changevisualization.tree;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.Enumeration;
import java.util.List;
import java.util.Vector;

import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.Icon;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTree;
import javax.swing.border.TitledBorder;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import tools.vitruv.change.testutils.changevisualization.ChangeVisualizationUI;
import tools.vitruv.change.testutils.changevisualization.common.ChangeDataSet;
import tools.vitruv.change.testutils.changevisualization.ui.ChangeComponent;
import tools.vitruv.change.testutils.changevisualization.ui.LabelValuePanel;

/**
 * A ChangeTree visualizes propagation results in the form of a tree.
 * 
 * @author Andreas Loeffler
 */
public class ChangeTree extends ChangeComponent {

	/**
	 * Needed for eclipse to stop warning about serialVersionIds. This feature will never been used. 
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * Enum of all different default expand states of newly added change data sets
	 * 
	 * @author Andreas Loeffler
	 */
	private static enum ExpandBehavior{
		EXPAND_PROPAGATED_CHANGES,
		EXPAND_VCHANGES,
		EXPAND_ECHANGES
	}
	
	/**
	 * The default setting for the simple echange text checkbox
	 */
	private static final boolean SIMPLE_ECHANGE_TEXT_DEFAULT_VALUE=false;
	
	/**
	 * The default setting for the show id checkbox
	 */
	private static final boolean SHOW_ID_DEFAULT_VALUE=false;

	/**
	 * Key used to store the viewport rectangle of the treeScroller in the changeDataSet
	 */
	private static final String VIEWPORT_RECTANGLE_KEY="VIEWPORT_RECTANGLE";

	/**
	 * The JTree that actually visualizes the changeDataSet
	 */
	private JTree treeUI;

	/**
	 * When details of an selected node are just text details, they are displayed in this JTextArea
	 */
	private JTextArea detailsUI;

	/**
	 * The actual default expand behaviour
	 */
	private ExpandBehavior expandBehavior=ExpandBehavior.EXPAND_PROPAGATED_CHANGES;

	/**
	 * The renderer that renders the nodes of the tree
	 */
	private final ChangeTreeNodeRenderer changeEventTreeRenderer;

	/**
	 * The splitpane splitting tree from details
	 */
	private JSplitPane detailsSplitpane;

	/**
	 * The JScrollPane used to display detail-component bigger than the viewport available 
	 */
	private JScrollPane detailsScroller;

	/**
	 * The TreeMouseListener responding to mouse interaction
	 */
	private final TreeMouseListener ml;

	/**
	 * The listener processing tree selection events of the JTree component
	 */
	private final TreeSelectionListener tsl=new TreeSelectionListener() {
		@Override
		public void valueChanged(TreeSelectionEvent e) {
			//reset details display to default behavior
			detailsUI.setText("");
			int divLocation=detailsSplitpane.getDividerLocation();
			detailsSplitpane.setRightComponent(detailsScroller);

			//We have single selection mode ==> only one selected path
			TreePath path = e.getNewLeadSelectionPath();				

			//Update the detailsUI (if necessary)
			updateDetailsUI(path);

			//Restore the divider location, changing of the displayed components could have changed it
			detailsSplitpane.setDividerLocation(divLocation);
		}	

		/**
		 * Updates the detailsUI, if necessary
		 * 
		 * @param path The path pointing to the selected node
		 */
		private void updateDetailsUI(TreePath path){
			if(path!=null) {							
				DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) path.getLastPathComponent();
				if(selectedNode!=null) {
					Object userObj=selectedNode.getUserObject();
					if(userObj!=null) {
						if(userObj instanceof FeatureNode) {
							String details=((FeatureNode)userObj).getDetails();
							String[][] detailsArray=((FeatureNode)userObj).getDetailsArray();
							Component detailsComp=((FeatureNode)userObj).getDetailsUI();
							if(details!=null) {
								detailsUI.setText(details);
							}else if(detailsArray!=null) {								
								detailsSplitpane.setRightComponent(new LabelValuePanel(detailsArray));
							}else if(detailsComp!=null) {								
								detailsSplitpane.setRightComponent(detailsComp);
							}
						}else if(userObj instanceof ChangeNode) {
							//ChangeNode creates the component on the fly for us
							Component detailsComp=((ChangeNode)userObj).getDetailsUI();
							detailsSplitpane.setRightComponent(detailsComp);							
						}
					}
				}
			}
		}
	};

	/**
	 * The actual changeDataSet
	 */
	private TreeChangeDataSet actualChangeDataSet=null;

	/**
	 * The JScrollPane used to scroll the tree, if necessary
	 */
	private JScrollPane treeScroller;

	/**
	 * The checkbox to select the simple echange text mode
	 */
	private JCheckBox simpleEChangeTextBox;

	/**
	 * The checkbox to enable/disable id showing
	 */
	private JCheckBox showIDTextBox;

	/**
	 * Constructs a ChangeTree UI visualizing change events in the form of a tree
	 */
	public ChangeTree(TabHighlighting tabHighlighting) {
		super(new BorderLayout());
		
		createUI();
		this.changeEventTreeRenderer = new ChangeTreeNodeRenderer(tabHighlighting);
		
		//Add listeners and renderes to the treeUI and scrollpane
		ml=new TreeMouseListener(tabHighlighting);
		treeUI.addMouseListener(ml);

		treeUI.getSelectionModel().addTreeSelectionListener(tsl);

		treeUI.setCellRenderer(changeEventTreeRenderer);

		treeScroller.addMouseWheelListener(new MouseWheelListener() {
			@Override
			public void mouseWheelMoved(MouseWheelEvent e) {	
				//Implements the usual strg + mousewheel behaviour for zooming
				if ((e.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) == 0) return;
				if(e.getWheelRotation()<=-1) {
					float newSize=treeUI.getFont().getSize()+2;
					if(newSize>30) newSize=30;
					treeUI.setFont(treeUI.getFont().deriveFont(newSize));
					treeUI.setRowHeight((int)(newSize+10));
				}else if(e.getWheelRotation()>=1) {
					float newSize=treeUI.getFont().getSize()-2;
					if(newSize<5) newSize=5;
					treeUI.setFont(treeUI.getFont().deriveFont(newSize));
					treeUI.setRowHeight((int)(newSize+10));
				}
			}
		});
	}
	
	public void setEnabled(boolean isEnabled) {
		changeEventTreeRenderer.setEnabled(isEnabled);
		if (isEnabled) {
			treeUI.addMouseListener(ml);
		} else {
			treeUI.removeMouseListener(ml);
		}
	}

	/**
	 * Creates the UI
	 */
	private void createUI() {		
		detailsSplitpane=new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);

		treeUI=new JTree();
		treeUI.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
		treeUI.setFont(ChangeVisualizationUI.DEFAULT_TREE_FONT);

		treeScroller=new JScrollPane(treeUI);
		detailsSplitpane.add(treeScroller);		

		detailsUI=new JTextArea();	
		detailsUI.setFont(ChangeVisualizationUI.DEFAULT_TEXTAREA_FONT);		
		detailsUI.setEditable(false);
		detailsScroller=new JScrollPane(detailsUI);

		TitledBorder detailsTitleBorder = BorderFactory.createTitledBorder("Details");
		detailsTitleBorder.setTitleFont(ChangeVisualizationUI.DEFAULT_TITLED_BORDER_FONT);
		detailsScroller.setBorder(detailsTitleBorder);
		detailsSplitpane.add(detailsScroller);		
		detailsSplitpane.setDividerLocation(1100);

		add(detailsSplitpane,BorderLayout.CENTER);

		//Create south == the toolbar
		add(createToolbar(),BorderLayout.SOUTH);
	}

	/**
	 * Creates the toolbar
	 * 
	 * @return The toolbar
	 */
	private Component createToolbar() {
		JPanel toolbar=new JPanel(new FlowLayout());

		addDefaultExpansionBehaviour(toolbar);

		toolbar.add(new JLabel("     "));//White space
		addEChangeClassColors(toolbar);

		toolbar.add(new JLabel("     "));//White space
		addStateChangeBoxes(toolbar);

		return toolbar;
	}

	/**
	 * Adds the state change boxes to the toolbar
	 * 
	 * @param toolbar The toolbar to add the boxes to
	 */
	private void addStateChangeBoxes(JPanel toolbar) {
		simpleEChangeTextBox=new JCheckBox("Simple EChange text",SIMPLE_ECHANGE_TEXT_DEFAULT_VALUE);
		simpleEChangeTextBox.setFont(ChangeVisualizationUI.DEFAULT_CHECKBOX_FONT);
		simpleEChangeTextBox.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				boolean simpleEChangeText=((JCheckBox)e.getSource()).isSelected();				
				updateSimpleEChangeText(simpleEChangeText);
			}			
		});
		toolbar.add(simpleEChangeTextBox);
		
		showIDTextBox=new JCheckBox("Show IDs",SHOW_ID_DEFAULT_VALUE);
		showIDTextBox.setFont(ChangeVisualizationUI.DEFAULT_CHECKBOX_FONT);
		showIDTextBox.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				boolean showID=((JCheckBox)e.getSource()).isSelected();				
				updateShowID(showID);
			}			
		});
		toolbar.add(showIDTextBox);

	}

	/**
	 * Update the simpleEChangeText state for all nodes in the model
	 * 
	 * @param simpleEChangeText The simpleEChangeText state
	 */
	private void updateSimpleEChangeText(boolean simpleEChangeText) {
		DefaultTreeModel model = (DefaultTreeModel)treeUI.getModel();
		DefaultMutableTreeNode root=(DefaultMutableTreeNode) model.getRoot();
		if(root!=null) {
			Enumeration nodes = root.depthFirstEnumeration();
			while(nodes.hasMoreElements()) {
				TreeNode node = nodes.nextElement();
				if(node instanceof DefaultMutableTreeNode) {
					DefaultMutableTreeNode mutableNode = (DefaultMutableTreeNode)node;
					if(mutableNode.getUserObject() instanceof ChangeNode) {
						//Only EChange nodes are affected and change their size
						((ChangeNode)mutableNode.getUserObject()).setSimpleEChangeText(simpleEChangeText);
						model.nodeChanged(node);
					}
				}
			}
		}
	}

	/**
	 * Update the showId state for all nodes in the model
	 * 
	 * @param showID The showID state
	 */
	private void updateShowID(boolean showID) {
		DefaultTreeModel model = (DefaultTreeModel)treeUI.getModel();
		DefaultMutableTreeNode root=(DefaultMutableTreeNode) model.getRoot();
		if(root!=null) {
			Enumeration nodes = root.depthFirstEnumeration();
			while(nodes.hasMoreElements()) {
				TreeNode node = nodes.nextElement();
				if(node instanceof DefaultMutableTreeNode) {
					DefaultMutableTreeNode mutableNode = (DefaultMutableTreeNode)node;
					if (mutableNode.getUserObject() instanceof ChangeNode) {
						//Only EChange nodes are affected and change their size
						((ChangeNode)mutableNode.getUserObject()).setShowID(showID);
						model.nodeChanged(node);
					}
				}
			}
		}
	}

	/**
	 * Adds labels to the toolbar visualizing the colors used for the different eChange types
	 * @param toolbar The toolbar to add the labels to
	 */
	private void addEChangeClassColors(JPanel toolbar) {
		
		JLabel changesLabel=new JLabel("EChange-Types");
		changesLabel.setFont(ChangeVisualizationUI.DEFAULT_LABEL_FONT);
		toolbar.add(changesLabel);
		
		Icon[] icons=new Icon[] {
				ChangeTreeNodeRenderer.ATTRIBUTE_ECHANGE_OPEN_ICON,
				ChangeTreeNodeRenderer.REFERENCE_ECHANGE_OPEN_ICON,
				ChangeTreeNodeRenderer.EXISTENCE_ECHANGE_OPEN_ICON,
				ChangeTreeNodeRenderer.ROOT_ECHANGE_OPEN_ICON
		};
		String[] names=new String[] {
				"Attribute",
				"Reference",
				"Existence",
				"Root"
		};

		for(int n=0;n rootChildren = rootNode.children();
			while(rootChildren.hasMoreElements()) {
				TreeNode propChangeNode=rootChildren.nextElement();
				if(expandBehavior==ExpandBehavior.EXPAND_VCHANGES) {
					//this means expand all propagated changes
					TreePath path=new TreePath(new TreeNode[] {
							rootNode,
							propChangeNode
					});				
					treeUI.expandPath(path);
				}else {					
					Enumeration propChangeChildren = propChangeNode.children();
					while(propChangeChildren.hasMoreElements()) {
						TreeNode vChangeNode=propChangeChildren.nextElement();
						TreePath path=new TreePath(new TreeNode[] {
								rootNode,
								propChangeNode,
								vChangeNode
						});				
						treeUI.expandPath(path);
					}
				}
			}
		}

		//reapply selection
		if(selPath!=null) {
			//Assure it is visible, which may not be the case depending on the selected expansion behaviour
			treeUI.expandPath(selPath);
			//make it selected
			treeUI.getSelectionModel().setSelectionPath(selPath);
		}
	}

	/**
	 * Collapse all nodes in the tree
	 */
	private void collapseAll() {
		int row = treeUI.getRowCount() - 1;
		while (row >= 0) {
			treeUI.collapseRow(row);
			row--;
		}
	}

	@Override
	public List determineHighlightedCdsIds(String highlightID, List changeDataSets) {
		if(highlightID==null)return null;
		if(changeDataSets==null)return null;
		List highlightedCdsIds=new Vector();
		
		//Walk all changeDataSets and store their if if any of their nodes should be highlighted
		for(ChangeDataSet cds:changeDataSets) {
			if(cds==null||!(cds instanceof TreeChangeDataSet)) {
				continue;
			}
			TreeChangeDataSet tcds=(TreeChangeDataSet)cds;
			DefaultMutableTreeNode rootNode=(DefaultMutableTreeNode) tcds.getData();
			String cdsID=rootNode.toString();
			
			//Rely on the renderers decision upon highlighting a given id
			if(ChangeTreeNodeRenderer.shouldHighlightNode(highlightID,rootNode)) {
				highlightedCdsIds.add(cdsID);
			}
		}
		return highlightedCdsIds;		
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy