/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.gui.swing.contexttree;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.table.TableCellEditor;
import javax.swing.tree.TreePath;
import org.geotools.gui.swing.contexttree.column.TreeTableColumn;
import org.geotools.gui.swing.contexttree.node.SubNodeGroup;
import org.geotools.gui.swing.contexttree.renderer.DefaultContextTreeHeaderRenderer;
import org.geotools.gui.swing.contexttree.renderer.HeaderInfo;
import org.geotools.gui.swing.misc.FacilitiesFactory;
import org.geotools.map.MapContext;
import org.geotools.map.MapLayer;
import org.jdesktop.swingx.renderer.DefaultTreeRenderer;
import org.jdesktop.swingx.treetable.TreeTableModel;
/**
* TreeTable
*
* @author Johann Sorel
*/
final class TreeTable extends org.jdesktop.swingx.JXTreeTable {
private static final TreePath[] EMPTY_PATH = {};
private static ResourceBundle BUNDLE = ResourceBundle.getBundle("org/geotools/gui/swing/contexttree/Bundle");
/**
* Default copy action used for Key Input
*/
private final Action COPY_ACTION = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
copySelectionInBuffer();
}
};
/**
* Default cut action used for Key Input
*/
private final Action CUT_ACTION = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
cutSelectionInBuffer();
}
};
/**
* Default paste action used for Key Input
*/
private final Action PASTE_ACTION = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
pasteBuffer();
}
};
/**
* Default delete action used for Key Input
*/
private final Action DELETE_ACTION = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
deleteSelection();
}
};
/**
* Default duplicate action used for Key Input
*/
private final Action DUPLICATE_ACTION = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
duplicateSelection();
}
};
/**
* the buffer containing the cutted/copied datas
*/
private final List<Object> buffer = new ArrayList<Object>();
private JContextTreePopup popupManager;
private TreeSelectionManager selectionManager;
/**
* String added to layer name use when paste/duplicate
*/
private String PREFIX = "-" + BUNDLE.getString("a_copy") + "- ";
/**
* Tree widget to manage MapContexts and MapLayers
*
*/
TreeTable(JContextTree frame) {
super(new ContextTreeModel(frame));
init(frame);
}
private void init(JContextTree frame){
putClientProperty("JTree.lineStyle", "Angled");
selectionManager = new TreeSelectionManager(frame);
popupManager = new JContextTreePopup(this, frame);
setComponentPopupMenu(popupManager.getPopupMenu());
setColumnControlVisible(true);
setTreeCellRenderer(new DefaultTreeRenderer(new TreeNodeProvider(frame)));
getTableHeader().setDefaultRenderer(new DefaultContextTreeHeaderRenderer());
initCellEditAcceleration();
initDragAndDrop();
initKeySupport();
String name = BUNDLE.getString("col_tree");
getColumnModel().getColumn(0).setHeaderValue(new HeaderInfo(name, " ", null));
getTreeSelectionModel().addTreeSelectionListener(selectionManager);
}
/**
* add mouse listener to set cell in edit mode when mouseover
*/
private void initCellEditAcceleration() {
//listener to set cell in edit mode on mouse over
this.addMouseMotionListener(new MouseMotionListener() {
public void mouseDragged(MouseEvent e) {
}
public void mouseMoved(MouseEvent e) {
Point p = e.getPoint();
if (p != null) {
int row = rowAtPoint(p);
int col = columnAtPoint(p);
if (row != editingRow || col != editingColumn) {
if (isEditing()) {
TableCellEditor editor = cellEditor;
if (!editor.stopCellEditing()) {
editor.cancelCellEditing();
}
}
if (!isEditing() && col >= 0 && row >= 0) {
//we handle differently ContextTreeColumn
if (getColumnExt(col) instanceof TreeTableColumn) {
TreeTableColumn column = (TreeTableColumn) getColumnExt(col);
if (isCellEditable(row, col) && column.isEditableOnMouseOver()) {
editCellAt(row, col);
}
}
}
}
}
}
});
}
private void initDragAndDrop() {
setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
DADContextTreeTransferHandler handler = new DADContextTreeTransferHandler();
setTransferHandler(handler);
setDropTarget(new DADContextTreeDrop(handler));
setDragEnabled(true);
}
private void initKeySupport() {
InputMap inputMap = getInputMap();
ActionMap actionMap = getActionMap();
KeyStroke copyKeys = KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.CTRL_MASK);
KeyStroke cutKeys = KeyStroke.getKeyStroke(KeyEvent.VK_X, KeyEvent.CTRL_MASK);
KeyStroke pasteKeys = KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.CTRL_MASK);
KeyStroke deleteKeys = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0);
KeyStroke duplicateKeys = KeyStroke.getKeyStroke(KeyEvent.VK_D, KeyEvent.CTRL_MASK);
String copycode = "copy";
String cutcode = "cut";
String pastecode = "paste";
String deletecode = "delete";
String duplicatecode = "duplicate";
inputMap.put(copyKeys, copycode);
inputMap.put(cutKeys, cutcode);
inputMap.put(pasteKeys, pastecode);
inputMap.put(deleteKeys, deletecode);
inputMap.put(duplicateKeys, duplicatecode);
actionMap.put(copycode, COPY_ACTION);
actionMap.put(cutcode, CUT_ACTION);
actionMap.put(pastecode, PASTE_ACTION);
actionMap.put(deletecode, DELETE_ACTION);
actionMap.put(duplicatecode, DUPLICATE_ACTION);
}
private MapContext findContext(TreePath tp) {
ContextTreeNode lastnode = (ContextTreeNode) tp.getLastPathComponent();
Object obj = lastnode.getUserObject();
if (obj instanceof MapContext) {
return (MapContext) obj;
} else if (lastnode.getParent().equals(getTreeTableModel().getRoot())) {
return null;
} else {
return findContext(new TreePath(lastnode.getParent()));
}
}
private MapLayer findLayer(TreePath tp) {
ContextTreeNode lastnode = (ContextTreeNode) tp.getLastPathComponent();
Object obj = lastnode.getUserObject();
if (obj instanceof MapLayer) {
return (MapLayer) obj;
} else if (lastnode.getParent().equals(getTreeTableModel().getRoot())) {
return null;
} else {
return findLayer(new TreePath(lastnode.getParent()));
}
}
TreeSelectionManager getSelectionManager() {
return selectionManager;
}
JContextTreePopup getPopupMenu() {
return popupManager;
}
List<Object> getSelectionList() {
TreePath[] selections = getTreeSelectionModel().getSelectionPaths();
List<Object> temp = new ArrayList<Object>();
if (hasSelection(selections)) {
for (TreePath tp : selections) {
temp.add(((ContextTreeNode) tp.getLastPathComponent()).getUserObject());
}
}
return temp;
}
/**
* get the tree model. dont play with the model, too much things are linked
* @return the tree model
*/
@Override
public ContextTreeModel getTreeTableModel() {
return (ContextTreeModel) super.getTreeTableModel();
}
/**
* set the tree model. dont play with the model, too much things are linked
* @param contexttreemodel the new model, <b>MUST</b> be a ContextTreeModel.
*
*/
@Override
public void setTreeTableModel(TreeTableModel contexttreemodel) {
if (contexttreemodel != null) {
if (contexttreemodel instanceof ContextTreeModel) {
super.setTreeTableModel(contexttreemodel);
} else {
}
}
}
////////////////////////////////////////////////////////////////////////////////
// CUT/COPY/PASTE/DUPLICATE/DELETE ////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
private boolean hasSelection(TreePath[] selections) {
if (selections != null) {
if (selections.length > 0) {
return true;
}
}
return false;
}
public boolean onlyMapContexts(TreePath[] paths) {
if(paths == null){
return false;
}
for (TreePath path : paths) {
if (!(((ContextTreeNode) path.getLastPathComponent()).getUserObject() instanceof MapContext)) {
return false;
}
}
return true;
}
public boolean onlyMapContexts(List<Object> buf) {
for (Object obj : buf) {
if (!(obj instanceof MapContext)) {
return false;
}
}
return true;
}
public boolean onlyMapLayers(TreePath[] paths) {
if(paths == null){
return false;
}
for (TreePath path : paths) {
if (!(((ContextTreeNode) path.getLastPathComponent()).getUserObject() instanceof MapLayer)) {
return false;
}
}
return true;
}
public boolean onlyMapLayers(List<Object> buf) {
for (Object obj : buf) {
if (!(obj instanceof MapLayer)) {
return false;
}
}
return true;
}
/**
* prefix string used when pasting/duplicating datas
*
* @param prefix if null, prefix will be an empty string
*/
void setPrefixString(String prefix) {
if (prefix != null) {
PREFIX = prefix;
} else {
PREFIX = "";
}
}
/**
* prefix used when pasting/duplicating datas
*
* @return String
*/
String getPrefixString() {
return PREFIX;
}
/**
*
* @return true if ther is something selected
*/
boolean hasSelection() {
TreePath[] selections = getTreeSelectionModel().getSelectionPaths();
if (selections != null) {
if (selections.length > 0) {
return true;
}
}
return false;
}
/**
* Duplicate was is actually selected in the tree. nothing happens
* if selection isn't composed of only 1 type of datas. (only layers or only contexts )
*
* @return true if duplication succeed
*/
boolean duplicateSelection() {
TreePath[] selections = getTreeSelectionModel().getSelectionPaths();
if(selections == null){
selections = EMPTY_PATH;
}
if (canDuplicateSelection()) {
FacilitiesFactory ff = new FacilitiesFactory();
if (onlyMapLayers(selections)) {
for (TreePath tp : selections) {
MapLayer layer = (MapLayer) ((ContextTreeNode) tp.getLastPathComponent()).getUserObject();
MapContext parent = (MapContext) ((ContextTreeNode) ((ContextTreeNode) tp.getLastPathComponent()).getParent()).getUserObject();
MapLayer copylayer = ff.duplicateLayer(layer);
copylayer.setTitle(PREFIX + layer.getTitle());
parent.addLayer(copylayer);
parent.moveLayer(parent.indexOf(copylayer), parent.indexOf(layer));
}
return true;
} else if (onlyMapContexts(selections)) {
for (TreePath tp : selections) {
MapContext context = (MapContext) ((ContextTreeNode) tp.getLastPathComponent()).getUserObject();
MapContext copycontext = ff.duplicateContext(context);
copycontext.setTitle(PREFIX + context.getTitle());
getTreeTableModel().addMapContext(copycontext);
}
return true;
}
}
return false;
}
/**
*
* @return true if tree buffer is empty
*/
boolean isBufferEmpty() {
if (buffer.size() == 0) {
return true;
} else {
return false;
}
}
/**
*
* @return true is paste can succeed
*/
boolean canPasteBuffer() {
if (isBufferEmpty()) {
return false;
} else {
if (onlyMapContexts(buffer)) {
return true;
} else if (onlyMapLayers(buffer)) {
TreePath[] selections = getTreeSelectionModel().getSelectionPaths();
if (hasSelection(selections)) {
if (selections.length == 1) {
return true;
}
}
}
}
return false;
}
/**
*
* @return true if duplication can succeed
*/
boolean canDuplicateSelection() {
TreePath[] selections = getTreeSelectionModel().getSelectionPaths();
if(selections == null){
selections = EMPTY_PATH;
}
if (hasSelection(selections)) {
return (onlyMapContexts(selections) || onlyMapLayers(selections));
} else {
return false;
}
}
/**
*
* @return true if delete can succeed
*/
boolean canDeleteSelection() {
return canDuplicateSelection();
}
/**
*
* @return true if copy can succeed
*/
boolean canCopySelection() {
return canDuplicateSelection();
}
/**
*
* @return true if cut can succeed
*/
boolean canCutSelection() {
return canDuplicateSelection();
}
/**
* delete what is actually selected
*
* @return true if delete suceed
*/
boolean deleteSelection() {
if (canDeleteSelection()) {
TreePath[] selections = getTreeSelectionModel().getSelectionPaths();
if(selections == null){
selections = EMPTY_PATH;
}
for (int i = selections.length - 1; i >= 0; i--) {
TreePath tp = selections[i];
if (((ContextTreeNode) tp.getLastPathComponent()).getUserObject() instanceof MapLayer) {
MapLayer layer = (MapLayer) ((ContextTreeNode) tp.getLastPathComponent()).getUserObject();
MapContext parent = (MapContext) ((ContextTreeNode) ((ContextTreeNode) tp.getLastPathComponent()).getParent()).getUserObject();
parent.removeLayer(layer);
} else if (((ContextTreeNode) tp.getLastPathComponent()).getUserObject() instanceof MapContext) {
MapContext context = (MapContext) ((ContextTreeNode) tp.getLastPathComponent()).getUserObject();
getTreeTableModel().removeMapContext(context);
}
}
return true;
}
return false;
}
/**
* copy what is actually selected in the tree buffer
*
* @return true if copy succeed
*/
boolean copySelectionInBuffer() {
TreePath[] selections = getTreeSelectionModel().getSelectionPaths();
if (hasSelection(selections)) {
buffer.clear();
if (onlyMapLayers(selections)) {
for (TreePath tp : selections) {
ContextTreeNode lastnode = (ContextTreeNode) tp.getLastPathComponent();
MapLayer layer = (MapLayer) lastnode.getUserObject();
buffer.add(layer);
}
return true;
} else if (onlyMapContexts(selections)) {
for (TreePath tp : selections) {
ContextTreeNode lastnode = (ContextTreeNode) tp.getLastPathComponent();
MapContext context = (MapContext) lastnode.getUserObject();
buffer.add(context);
}
return true;
}
}
return false;
}
/**
* copy what is actually selected in the tree buffer and cut it from the tree.
*
* @return true if cut succeed
*/
boolean cutSelectionInBuffer() {
TreePath[] selections = getTreeSelectionModel().getSelectionPaths();
if (hasSelection(selections)) {
buffer.clear();
if (onlyMapLayers(selections)) {
for (TreePath path : selections) {
ContextTreeNode childNode = (ContextTreeNode) path.getLastPathComponent();
ContextTreeNode parentNode = (ContextTreeNode) childNode.getParent();
buffer.add(childNode.getUserObject());
((MapContext) parentNode.getUserObject()).removeLayer((MapLayer) childNode.getUserObject());
}
return true;
} else if (onlyMapContexts(selections)) {
for (TreePath path : selections) {
ContextTreeNode parentNode = (ContextTreeNode) path.getLastPathComponent();
buffer.add(parentNode.getUserObject());
removeMapContext((MapContext) parentNode.getUserObject());
}
return true;
}
}
return false;
}
/**
* paste at the selected node what is in the buffer
*
* @return true if paste succeed
*/
boolean pasteBuffer() {
TreePath[] selections = getTreeSelectionModel().getSelectionPaths();
if (!isBufferEmpty()) {
FacilitiesFactory ff = new FacilitiesFactory();
if (onlyMapLayers(buffer)) {
if (hasSelection(selections)) {
if (selections.length == 1) {
if (((ContextTreeNode) selections[0].getLastPathComponent()).getUserObject() instanceof MapLayer) {
MapLayer insertlayer = (MapLayer) ((ContextTreeNode) selections[0].getLastPathComponent()).getUserObject();
MapContext parent = (MapContext) ((ContextTreeNode) ((ContextTreeNode) selections[0].getLastPathComponent()).getParent()).getUserObject();
for (Object data : buffer) {
MapLayer layer = (MapLayer) data;
if (parent.indexOf(layer) == -1) {
parent.addLayer(layer);
parent.moveLayer(parent.indexOf(layer), parent.indexOf(insertlayer));
} else {
MapLayer copy = ff.duplicateLayer(layer);
copy.setTitle(PREFIX + layer.getTitle());
parent.addLayer(copy);
parent.moveLayer(parent.indexOf(copy), parent.indexOf(insertlayer));
}
}
} else if (((ContextTreeNode) selections[0].getLastPathComponent()).getUserObject() instanceof MapContext) {
MapContext context = (MapContext) ((ContextTreeNode) selections[0].getLastPathComponent()).getUserObject();
for (Object data : buffer) {
MapLayer layer = (MapLayer) data;
if (context.indexOf(layer) == -1) {
context.addLayer(layer);
} else {
MapLayer copy = ff.duplicateLayer(layer);
copy.setTitle(PREFIX + layer.getTitle());
context.addLayer(copy);
context.moveLayer(context.indexOf(copy), context.indexOf(layer));
}
}
}
return true;
}
}
} else if (onlyMapContexts(buffer)) {
for (Object data : buffer) {
MapContext context = (MapContext) data;
if (getMapContextIndex(context) == -1) {
addMapContext(context);
} else {
addMapContext(ff.duplicateContext(context));
}
}
buffer.clear();
return true;
}
}
return false;
}
/**
* get a Array of the objects in the buffer
*
* @return object array, can be MapLayers or MapContexts or empty array
*/
Object[] getBuffer() {
return buffer.toArray(new Object[buffer.size()]);
}
void clearBuffer() {
buffer.clear();
}
////////////////////////////////////////////////////////////////////////////////
// COLUMNS MANAGEMENT //////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/**
* add a new column in the model and update the treetable
* @param model the new column model
*/
void addColumnModel(TreeTableColumn model) {
getTreeTableModel().addColumnModel(model);
getColumnModel().addColumn(model);
revalidate();
}
/**
* remove column
* @param model
*/
void removeColumnModel(TreeTableColumn model) {
getTreeTableModel().removeColumnModel(model);
getColumnModel().removeColumn(model);
revalidate();
}
/**
* remove column at index column
* @param column
*/
void removeColumnModel(int column) {
getTreeTableModel().removeColumnModel(column);
getColumnModel().removeColumn(getColumnModel().getColumn(column));
revalidate();
}
int getColumnModelIndex(TreeTableColumn model) {
return getTreeTableModel().getColumnModelIndex(model);
}
public int getColumnModelCount() {
return getTreeTableModel().getColumnModelCount();
}
/**
* get the list of column
* @return list of column models
*/
TreeTableColumn[] getColumnModels() {
return getTreeTableModel().getColumnModels().toArray(new TreeTableColumn[0]);
}
////////////////////////////////////////////////////////////////////////////////
// SUBNODES MANAGEMENT /////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void addSubNodeGroup(SubNodeGroup group) {
getTreeTableModel().addSubNodeGroup(group);
revalidate();
}
void removeSubNodeGroup(SubNodeGroup group) {
getTreeTableModel().removeSubNodeGroup(group);
}
void removeSubNodeGroup(int index) {
getTreeTableModel().removeSubNodeGroup(index);
}
int getSubNodeGroupCount() {
return getTreeTableModel().getSubNodeGroupCount();
}
int getSubNodeGroupIndex(SubNodeGroup group) {
return getTreeTableModel().getSubNodeGroupIndex(group);
}
SubNodeGroup[] getSubNodeGroups() {
return getTreeTableModel().getSubNodeGroups();
}
////////////////////////////////////////////////////////////////////////////////
// MAPCONTEXT MANAGEMENT ///////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/**
* get the active context
* @return return the active MapContext, if none return null
*/
MapContext getActiveContext() {
return getTreeTableModel().getActiveContext();
}
/**
* active the context if in the tree
* @param context the mapcontext to active
*/
void setActiveContext(MapContext context) {
getTreeTableModel().setActiveContext(context);
}
/**
* add context to the Tree if not allready in it
* @param context the context to add
*/
void addMapContext(MapContext context) {
getTreeTableModel().addMapContext(context);
expandPath(new TreePath(getTreeTableModel().getRoot()));
expandPath(new TreePath(getTreeTableModel().getMapContextNode(context)));
}
/**
* remove context from the tree
* @param context target mapcontext to remove
*/
void removeMapContext(MapContext context) {
getTreeTableModel().removeMapContext(context);
}
/**
* count MapContext in the tree
* @return number of mapcontext in the tree
*/
int getMapContextCount() {
return getTreeTableModel().getMapContextCount();
}
/**
* return context at index i
* @param i position of the mapcontext
* @return the mapcontext a position i
*/
MapContext getMapContext(int i) {
return getTreeTableModel().getMapContext(i);
}
/**
* get the index of a mapcontext in the tree
* @param context the mapcontext to find
* @return index of context
*/
int getMapContextIndex(MapContext context) {
return getTreeTableModel().getMapContextIndex(context);
}
/**
* MapContext Array
* @return empty Array if no mapcontexts in tree
*/
MapContext[] getMapContexts() {
return getTreeTableModel().getMapContexts();
}
/**
* move a mapcontext
* @param context the context to move
* @param newplace new position of the child node
*/
void moveMapContext(MapContext context, int newplace) {
ContextTreeNode moveNode = (ContextTreeNode) getTreeTableModel().getMapContextNode(context);
ContextTreeNode father = (ContextTreeNode) moveNode.getParent();
getTreeTableModel().moveMapContext(moveNode, father, newplace);
}
////////////////////////////////////////////////////////////////////////////////
// LISTENERS MANAGEMENT ////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/**
* add treeListener to Model
* @param ker the new listener
*/
void addTreeContextListener(TreeContextListener ker) {
getTreeTableModel().addTreeContextListener(ker);
}
/**
* remove treeListener from Model
* @param ker the listner to remove
*/
void removeTreeContextListener(TreeContextListener ker) {
getTreeTableModel().removeTreeContextListener(ker);
}
/**
* get treeListeners list
* @return the listener's table
*/
TreeContextListener[] getTreeContextListeners() {
return getTreeTableModel().getTreeContextListeners();
}
}