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

org.databene.edifatto.gui.browse.InterchangeTreeTable Maven / Gradle / Ivy

/*
 * Copyright (C) 2013-2015 Volker Bergmann ([email protected]).
 * All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.databene.edifatto.gui.browse;

import java.awt.Point;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Stack;

import javax.swing.AbstractAction;
import javax.swing.DefaultCellEditor;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.tree.TreePath;

import org.databene.commons.CollectionUtil;
import org.databene.edifatto.EdiFormatSymbols;
import org.databene.edifatto.definition.ComponentDefinition;
import org.databene.edifatto.definition.Definition;
import org.databene.edifatto.gui.EdiContentTableCellRenderer;
import org.databene.edifatto.gui.EdiNameTreeCellRenderer;
import org.databene.edifatto.gui.EdifattoGUI;
import org.databene.edifatto.gui.ParameterizationHelper;
import org.databene.edifatto.model.Component;
import org.databene.edifatto.model.EdiGroup;
import org.databene.edifatto.model.EdiItem;
import org.databene.edifatto.model.EdiProtocol;
import org.databene.edifatto.model.Interchange;
import org.databene.edifatto.util.EdiUtil;
import org.databene.edifatto.util.Parent;
import org.jdesktop.swingx.JXTreeTable;
import org.jdesktop.swingx.treetable.AbstractTreeTableModel;
import org.jdesktop.swingx.treetable.TreeTableModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * {@link JXTreeTable} implementation for displaying a tree with the data of an EDI interchange
 * accompanied by its structure information in a table layout.
 * Created: 28.06.2014 14:48:00
 * @since 2.0.0
 * @author Volker Bergmann
 */

public class InterchangeTreeTable extends JXTreeTable {

	private static final long serialVersionUID = 1L;
	
	private static final Logger LOGGER = LoggerFactory.getLogger(InterchangeTreeTable.class);
	
	public InterchangeTreeTable() {
		this(new Interchange(EdiProtocol.EDIFACT, EdiFormatSymbols.EDIFACT));
	}

	public InterchangeTreeTable(Interchange interchange) {
		super(new InterchangeTreeTableModel(interchange));
		setLargeModel(true);
		setTreeCellRenderer(new EdiNameTreeCellRenderer());
		setDefaultRenderer(EdiItem.class, new EdiContentTableCellRenderer());
		setDefaultEditor(EdiItem.class, new DefaultCellEditor(new JTextField()));
		setInterchange(interchange);
		KeyStroke findKeyStroke = KeyStroke.getKeyStroke('F', InputEvent.META_MASK);
		getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(findKeyStroke, "find");
		getModel().addTableModelListener(new TableModelListener() {
			@Override
			public void tableChanged(TableModelEvent evt) {
				Window gui = SwingUtilities.getWindowAncestor(InterchangeTreeTable.this);
				if (gui instanceof EdifattoGUI)
					((EdifattoGUI) gui).updateTitle();
			}
		});
		setUpPopup();
	}
	
	public boolean isFaultTolerant() {
		return getTreeTableModel().isFaultTolerant();
	}

	public void setFaultTolerant(boolean faultTolerant) {
		getTreeTableModel().setFaultTolerant(faultTolerant);
	}

	
	
	private void setUpPopup() {
		MouseListener ml = new MouseAdapter() {
			@Override
			public void mousePressed(MouseEvent e) { // popups triggered on Mac OS X
				handleClick(e);
			}

			@Override
			public void mouseReleased(MouseEvent e) { // popups triggered on Windows
				handleClick(e);
			}

			private void handleClick(MouseEvent e) {
				if (e.isPopupTrigger()) { 
					List affectedBeans = beansForPopupClick(e.getPoint());
					if (affectedBeans.size() == 1 && affectedBeans.get(0) instanceof Component)
						showPopup((Component) affectedBeans.get(0), e.getPoint());
				}
			}

			private void showPopup(Component component, Point p) {
				JPopupMenu popup = new JPopupMenu();
				popup.add(new CopyAction(component));
				popup.add(new PasteAction(component));
				popup.show(InterchangeTreeTable.this, p.x, p.y);
			}
		};
		this.addMouseListener(ml);
	}


	
	@Override
	public InterchangeTreeTableModel getTreeTableModel() {
		return (InterchangeTreeTableModel) super.getTreeTableModel();
	}

	public Interchange getInterchange() {
		return getTreeTableModel().getInterchange();
	}
	
	public void setInterchange(Interchange interchange) {
		getTreeTableModel().setInterchange(interchange);
		expandGroups();
	}

	public void expandGroups() {
		expandGroups(getTreeTableModel().getRoot(), new Stack());
	}

	private void expandGroups(Object node, Stack pathStack) {
		if ((node instanceof EdiGroup)) {
			pathStack.push(node);
			if (!pathStack.isEmpty()) {
				TreePath path = new TreePath(pathStack.toArray());
				expandPath(path);
			}
			InterchangeTreeTableModel model = getTreeTableModel();
			for (int i = 0; i < model.getChildCount(node); i++)
				expandGroups(model.getChild(node, i), pathStack);
			pathStack.pop();
		}
	}

	public List beansForPopupClick(Point clickedLocation) {
		EdiItem clickedItem = itemAtTableLocation(clickedLocation);
		List selectedBeans = getSelectedItems();
		List affectedBeans;
		if (clickedItem == null) {
			affectedBeans = new ArrayList();
		} else if (selectedBeans.contains(clickedItem)) {
			affectedBeans = selectedBeans;
		} else {
			int row = rowAtPoint(clickedLocation);
			getSelectionModel().setSelectionInterval(row, row);
			affectedBeans = CollectionUtil.toList(itemAtRow(row));
		}
		return affectedBeans;
	}

	private EdiItem itemAtTableLocation(Point p) {
		int rowIndex = rowAtPoint(p);
		return (rowIndex >= 0 ? itemAtRow(rowIndex) : null);
	}

	public EdiItem itemAtRow(int modelRowIndex) {
		return (EdiItem) getModel().getValueAt(modelRowIndex, 1);
	}
	
	public List getSelectedItems() {
		int[] rowIndices = getSelectedRows();
		List selectedItems = new ArrayList();
		for (int i = 0; i < rowIndices.length; i++)
			selectedItems.add(itemAtRow(rowIndices[i]));
		return selectedItems;
	}
	
	public static TreePath pathOfItem(EdiItem item) {
		Deque queue = new ArrayDeque();
		EdiItem tmp = item;
		do {
			queue.addFirst(tmp);
			tmp = tmp.getParent();
		} while (tmp != null);
		return new TreePath(queue.toArray());
	}
	
	public static class InterchangeTreeTableModel extends AbstractTreeTableModel implements TreeTableModel {
		
		private Interchange interchange;

		private boolean faultTolerant;
		
		public InterchangeTreeTableModel() {
			this(new Interchange(EdiProtocol.EDIFACT, EdiFormatSymbols.EDIFACT));
			this.faultTolerant = false;
		}
		
		public InterchangeTreeTableModel(Interchange root) {
			setInterchange(root);
		}

		public Interchange getInterchange() {
			return interchange;
		}
		
		public void setInterchange(Interchange interchange) {
			ParameterizationHelper.setItemName("interchange", interchange);
			ParameterizationHelper.setBeanClassName("Interchange", interchange);
			this.interchange = interchange;
			this.modelSupport.fireNewRoot();
		}

		public boolean isFaultTolerant() {
			return faultTolerant;
		}

		public void setFaultTolerant(boolean faultTolerant) {
			this.faultTolerant = faultTolerant;
		}

		@Override
		public Object getRoot() {
			return interchange;
		}

		@Override
		public int getChildCount(Object parent) {
			if (parent instanceof Parent)
				return ((Parent) parent).getChildCount();
			else
				return 0;
		}

		@Override
		public Object getChild(Object parent, int index) {
			if (parent instanceof Parent)
				return ((Parent) parent).getChild(index);
			else
				return null;
		}

		@Override
		public int getIndexOfChild(Object parent, Object child) {
			if (parent instanceof Parent)
				return getIndexOfParentChild((Parent) parent, child);
			else
				return -1;
		}

		private static int getIndexOfParentChild(Parent parent, Object child) {
			int n = parent.getChildCount();
			for (int i = 0; i < n; i++)
				if (parent.getChild(i) == child)
					return i;
			return -1;
		}

		@Override
		public boolean isLeaf(Object node) {
			return (node instanceof Component);
		}

		@Override
		public void valueForPathChanged(TreePath path, Object newValue) {
			throw new UnsupportedOperationException("Operation not supported");
		}

		@Override
		public int getColumnCount() {
			return 3;
		}

		@Override
		public int getHierarchicalColumn() {
			return 0;
		}

		@Override
		public String getColumnName(int column) {
			switch (column) {
				case 0: return "Nodes";
				case 1: return "Content";
				case 2: return "Description";
				default: return "?";
			}
		}
		
		@Override
		public Class getColumnClass(int columnIndex) {
			switch (columnIndex) {
				case 0 : return EdiItem.class;
				case 1 : return EdiItem.class;
				default : return String.class;
			}
		}
		
		@Override
		public Object getValueAt(Object node, int column) {
			switch (column) {
				case 0: return node;
				case 1: return node;
				case 2: return getDescription(node);
				default : return null;
			}
		}
		
		@Override
		public boolean isCellEditable(Object node, int column) {
			return (node instanceof Component && column == 1);
		}

		@Override
		public void setValueAt(Object value, Object item, int column) {
			if (item instanceof Component) {
				Component component = (Component) item;
				ComponentDefinition def = component.getDefinition();
				String enteredText = EdiUtil.escape((String) value, interchange.getSymbols());
				if (!enteredText.equals(component.getData())) {
					if (!faultTolerant) {
						int enteredLength = enteredText.length();
						if (enteredLength < def.getMinLength()) {
							String message = "Entered component text too short. Minimum length : " + def.getMinLength();
							JOptionPane.showMessageDialog(null, message, "Error", JOptionPane.ERROR_MESSAGE);
							return;
						}
						if (enteredLength > def.getMaxLength()) {
							String message = "Entered component text too long. Maximum length : " + def.getMaxLength();
							JOptionPane.showMessageDialog(null, message, "Error", JOptionPane.ERROR_MESSAGE);
							return;
						}
					}
					component.setData(enteredText);
					firePathChanged(pathOfItem(component));
				}
			}
		}

		public void firePathChanged(TreePath path) {
			modelSupport.firePathChanged(path);
		}

		public void fireChildRemoved(TreePath parentPath, int index, Object child) {
			modelSupport.fireChildRemoved(parentPath, index, child);
		}


		// private helpers -------------------------------------------------------------------------------------------------

		private static Object getDescription(Object node) {
			if (node instanceof EdiItem) {
				Definition definition = ((EdiItem) node).getDefinition();
				return (definition != null ? definition.getDocumentation() : null);
			} else
				return null;
		}

	}
	
	class CopyAction extends AbstractAction {
		
		private static final long serialVersionUID = 1L;

		private Component component;
		
		CopyAction(Component component) {
			super("Copy");
			this.component = component;
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
			StringSelection selection = new StringSelection(component.getData());
		    clipboard.setContents(selection, selection);
		}
	}

	class PasteAction extends AbstractAction {
		
		private static final long serialVersionUID = 1L;

		private Component component;
		
		PasteAction(Component component) {
			super("Paste");
			this.component = component;
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
			Transferable t = clipboard.getContents(null);
	        if (t.isDataFlavorSupported(DataFlavor.stringFlavor)) {
	            try {
					String data = (String) t.getTransferData(DataFlavor.stringFlavor);
					System.out.println("Clipboard contents: " + data);
					component.setData(data);
				} catch (Exception e1) {
					LOGGER.error("Paste failed", e1);
				}
	        }
		}
	}

}