
com.viaoa.jfc.OATree Maven / Gradle / Ivy
/* Copyright 1999-2015 Vince Via [email protected]
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 com.viaoa.jfc;
import java.awt.*;
import java.awt.dnd.*;
import java.awt.datatransfer.*;
import java.awt.event.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.lang.reflect.*;
import java.io.IOException;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.*;
import javax.swing.tree.*;
import sun.swing.SwingUtilities2;
import com.viaoa.object.*;
import com.viaoa.hub.*;
import com.viaoa.jfc.dnd.OATransferable;
import com.viaoa.util.*;
import com.viaoa.jfc.control.*;
import com.viaoa.jfc.tree.*;
import com.viaoa.jfc.table.*;
import com.viaoa.jfc.undo.OAUndoManager;
import com.viaoa.jfc.undo.OAUndoableEdit;
/**
JTree subclass that binds objects and collections to tree nodes.
Trees are built using Hubs for root nodes, and property paths for children nodes. The
property paths are based on the parent node. A title node can be used to act as a heading
for children nodes.
Nodes for trees are built using OANode objects that are then added to the tree as root nodes.
Nodes can then be added to other nodes to create children nodes. Recursive nodes can be built
by adding a node back to the tree.
All nodes work directly with Hubs and objects. The display value for the node is based on the property
path defined in the node.
Allows for multiple root nodes.
Trees can be customized by using renderer methods.
- Uses OATreeNode icon, font, background, foreground.
- Calls the select OATreeNode getTreeCellRendererComponent(...)
- Calls OATreeNode listeners getTreeCellRendererComponent(...)
- Calls OATree getTreeCellRendererComponent(...)
- Calls OATree listeners getTreeCellRendererComponent(...)
Navigation events/methods can be used to "know" when a node is acted upon.
- nodeSelected()
- objectSelected()
- onDoubleClick()
Sequence of events when a node is selected
- valueChanged(TreeSelectionEvent) is called
- nodeSelected(TreeSelectionEvent)
- valueChangedImpl(TreeSelectionEvent) depreicated
- nodeSelected(OATreeNodeData) is called for tree
- nodeSelected(OATreeNodeData) is called for selected OATreeNode
- nodeSelected(OATreeNodeData) is called for OATreeListeners for the Tree
- nodeSelected(OATreeNodeData) is called for OATreeListeners for the selected OATreeNode
- objectSelected(Object) is called for tree
- objectSelected(Object) is called for selected OATreeNode
- objectSelected(Object) is called for OATreeListeners for the selected OATreeNode
- objectSelected(Object) is called for OATreeListeners for the Tree
Nodes can be set up to have editors that are other OA components, ex: OATextField.
Popup menus can be added to nodes that allow for right click functionality.
Note: when adding recursive nodes (ex: node.add(node2)), make sure nodes are
completely setup before adding, since the node is cloned if it is already being used.
Example:
Simple tree showing for listing Employees. Other examples are listed in the methods and also
in OATreeNode
OATree tree = new OATree();
OATreeTitleNode nodeTitle = new OATreeTitleNode("Employees");
icon = Application.getIcon("employee.gif");
nodeTitle.setIcon(new ImageIcon("employee.gif");
nodeTitle.setPopupMenu(getEmployeesMenu());
add(nodeTitle);
node = new OATreeNode("fullName", hubEmployee);
node.setFont(getFont().deriveFont(font.ITALIC));
node.setAllowDrop(false);
node.setIconPropertyPath("employeeGifFileName");
node.setPopupMenu(getEmployeeMenu());
nodeTitle.add(node);
tree.setRowHeight(30);
tree.setColumns(25);
// setting an active node based on oaObjects
ItemCategory ic = getItemCategories().getAO();
Object[] objs = null;
for ( ;ic!= null; ic=ic.getParentItemCategory()) {
objs = (Object[]) OAArray.insert(Object.class, objs, ic, 0);
}
objs = (Object[]) OAArray.add(Object.class, objs, item);
OATree t = getTitleTreeNode().getTree();
t.setSelectedNode(objs);
@see OATreeNode
@see OATreeTitleNode
@see Hub2TreeNode
@see OATreeNodeData
@see #nodeSelected
*/
public class OATree extends JTree implements TreeExpansionListener, TreeSelectionListener, DragGestureListener, DropTargetListener {
private static Logger LOG = Logger.getLogger(OATree.class.getName());
protected OATreeNode root;
protected OATreeModel model;
protected int rows, columns, miniRows, miniColumns;
protected int widthColumn, miniWidthColumn;
protected OAObject hubObject;
protected Font fontDefault;
protected boolean bIsSelectingNode; // used to flag that valueChanged() event is updating hubs
protected boolean bIsSettingNode; // used to flag that valueChanged() called by hub update, also used by Hub2TreeNode
protected Object[] lastSelection = new Object[0];
protected boolean bAllowDrop = false;
protected boolean bAllowDrag = false;
protected DropTarget dropTarget;
protected DragSource dragSource;
protected boolean bUseIcon=true;
private int onAddNotifyExpandRow = -1;
private Object[] onAddNotifySelectNode; // if set before addNotify
private boolean bAddNotify;
private boolean bWaitOnAddNotify;
private boolean bStructureChanged;
private boolean onAddNotifyExpandAll;
private TreeCellRenderer oldRenderer;
private Vector vecListener;
private boolean bValueChangedCalled; // used to track mouseEvents, to know if valueChanged() was called when super.processMouse is called.
private Hub dragToHub;
private Object dragToObject;
private boolean dragAfter;
private OATreeNode dragToNode;
// used for DND support.
final static DragSourceListener dragSourceListener = new MyDragSourceListener();
// START Drag&Drop
static class MyDragSourceListener implements DragSourceListener {
public void dragEnter(DragSourceDragEvent e) {}
public void dragOver(DragSourceDragEvent e) { }
public void dropActionChanged(DragSourceDragEvent e) {}
public void dragExit(DragSourceEvent e) {}
public void dragDropEnd(DragSourceDropEvent e) {}
}
public OATree() {
this(0, 0);
}
public String getToolTipText() {
return super.getToolTipText();
}
public OATree(int columns) {
this(0, columns);
}
/**
Create a new OATree that has a width based on character size.
@param columns default width of tree based on average size of character.
@param rows used to call setVisibleRowCount()
*/
public OATree(int rows, int columns) {
this(rows, columns, true);
}
public OATree(int rows, int columns, boolean bWaitOnAddNotify) {
super(new Vector());
this.bWaitOnAddNotify = bWaitOnAddNotify;
if (!bWaitOnAddNotify) bAddNotify = true;
this.enableEvents(AWTEvent.FOCUS_EVENT_MASK);
setRootVisible(false);
root = new OATreeNode("");
root.def.tree = this;
model = new OATreeModel(this);
setModel(model);
setLargeModel(true);
// setDragEnabled(true);
setAutoscrolls(true); // not working, since OATree does it's own DND
setupRenderer();
setupEditor();
setPreferredSize(rows, columns);
getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
dragSource = DragSource.getDefaultDragSource();
dragSource.createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_COPY_OR_MOVE,this);
dropTarget = new DropTarget(this,this);
getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0,false), "ToggleNode");
getActionMap().put("ToggleNode", new AbstractAction() {
public void actionPerformed(ActionEvent e) {
int[] rows = getSelectionRows();
if (rows == null || rows.length == 0) return;
if (isExpanded(rows[0])) collapseRow(rows[0]);
else expandRow(rows[0]);
}
});
ToolTipManager.sharedInstance().registerComponent(this);
setExpandsSelectedPaths(true);
setBorder(new EmptyBorder(2,2,2,2));
}
public @Override void setDragEnabled(boolean b) {
super.setDragEnabled(false); // this is auto handled wihtin the class
}
/**
* Flag to use the folder icon. Default is true
* @param b
*/
public void setUseIcon(boolean b) {
this.bUseIcon = b;
}
public boolean getUseIcon() {
return bUseIcon;
}
public @Override void setLargeModel(boolean newValue) {
// must be true, since OATree is keeping track of nodes and nodeDatas
super.setLargeModel(true);
}
public @Override void setRootVisible(boolean b) {
super.setRootVisible(false); // needs to be false
}
/**
Flag that allows for drag & drop support, default=true.
*/
public void setAllowDnD(boolean b) {
setAllowDrop(b);
setAllowDrag(b);
}
public void setAllowDnd(boolean b) {
setAllowDrop(b);
setAllowDrag(b);
}
/**
Flag that allows for dropping (DND) support, default=true.
*/
public boolean getAllowDrop() {
return bAllowDrop;
}
/**
Flag that allows for dropping (DND) support, default=true.
*/
public void setAllowDrop(boolean b) {
bAllowDrop = b;
}
/**
Flag that allows for dragging (DND) support, default=true.
*/
public boolean getAllowDrag() {
return bAllowDrag;
}
/**
Flag that allows for dragging (DND) support, default=true.
*/
public void setAllowDrag(boolean b) {
bAllowDrag = b;
}
/**
Overloaded from JTree to always return true. (vv Not sure why)
*/
public boolean hasBeenExpanded(TreePath path) {
return true;
}
public boolean isSelectingNode() {
return bIsSelectingNode;
}
public boolean isSettingNode() {
return bIsSettingNode;
}
public void setSettingNode(Hub2TreeNode htn, boolean tf) {
bIsSettingNode = tf;
}
public OATreeNodeData getSelectedTreeNodeData() {
TreePath tp = getSelectionPath();
if (tp == null) return null;
Object[] objs = tp.getPath();
if (objs.length < 2) return null;
OATreeNodeData tnd = (OATreeNodeData) objs[objs.length-1];
return tnd;
}
public OATreeNode getSelectedTreeNode() {
TreePath tp = getSelectionPath();
if (tp == null) return null;
Object[] objs = tp.getPath();
if (objs.length < 2) return null;
OATreeNodeData tnd = (OATreeNodeData) objs[objs.length-1];
OATreeNode node = tnd.node;
return node;
}
public Object getSelectedObject() {
TreePath tp = getSelectionPath();
if (tp == null) return null;
Object[] objs = tp.getPath();
if (objs.length < 2) return null;
OATreeNodeData tnd = (OATreeNodeData) objs[objs.length-1];
OATreeNode node = tnd.node;
if (node.titleFlag) return null;
return tnd.object;
}
public Hub getSelectedHub() {
TreePath tp = getSelectionPath();
if (tp == null) return null;
Object[] objs = tp.getPath();
if (objs.length < 2) return null;
OATreeNodeData tnd = (OATreeNodeData) objs[objs.length-1];
OATreeNode node = tnd.node;
if (node.titleFlag) return null;
return tnd.getHub();
}
private OATreeNodeData tndDragging;
/** Drag and Drop support. */
public void dragGestureRecognized(DragGestureEvent e) {
if (!bAllowDrag) return;
Point pt = e.getDragOrigin();
int row = getRowForLocation(pt.x, pt.y);
if (row < 0) return;
TreePath tp = getPathForRow(row);
Object[] objs = tp.getPath();
if (objs.length < 2) return;
tndDragging = (OATreeNodeData) objs[objs.length-1];
OATreeNode node = tndDragging.node;
if (node.titleFlag) return;
if (!node.getAllowDrag()) return;
Hub h = tndDragging.getHub();
if (h == null) return;
OATransferable t = new OATransferable(h, tndDragging.object);
dragSource.startDrag(e, null, t, dragSourceListener);
}
/** Drag and Drop support. */
public void dragEnter(DropTargetDragEvent e) {
if (e.isDataFlavorSupported(OATransferable.HUB_FLAVOR)) {
e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
}
else {
e.rejectDrag();
}
}
public void dragOver(DropTargetDragEvent e) {
dragToHub = null;
dragToObject = null;
dragAfter = false;
dragToNode = null;
Point pt = e.getLocation();
dragOverNode(pt);
autoscroll(pt);
_dragOver(e);
if (dragToHub != null) {
e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
}
else {
e.rejectDrag();
}
}
private int lastDragOverRow;
private long lastDragOverTime;
// this will expand a row.
private void dragOverNode(Point location) {
TreePath path = getClosestPathForLocation(location.x, location.y);
long holdLastTime = lastDragOverTime;
int holdLastRow = lastDragOverRow;
lastDragOverTime = 0;
lastDragOverRow = -1;
if (path == null) return;
if (isExpanded(path)) return;
// setSelectionPath(path);
lastDragOverRow = getRowForPath(path);
long t = System.currentTimeMillis();
if (holdLastRow != lastDragOverRow || holdLastTime == 0) {
lastDragOverTime = t;
return;
}
lastDragOverTime = holdLastTime;
if (holdLastTime + 470 > t) return;
Object[] objs = path.getPath();
if (objs.length > 0) {
OATreeNodeData tnd = (OATreeNodeData) objs[objs.length-1];
if (tnd == tndDragging) return; // dont expand the node that is being dragged
}
expandPath(path);
}
private static Insets autoScrollInsets = new Insets(20, 20, 20, 20);
private void autoscroll(Point cursorLocation) {
Rectangle rectOuter = getVisibleRect();
Rectangle rectInner = new Rectangle(
rectOuter.x + autoScrollInsets.left,
rectOuter.y + autoScrollInsets.top,
rectOuter.width - (autoScrollInsets.left + autoScrollInsets.right),
rectOuter.height - (autoScrollInsets.top + autoScrollInsets.bottom));
if (!rectInner.contains(cursorLocation)) {
Rectangle rect = new Rectangle(
cursorLocation.x - autoScrollInsets.left,
cursorLocation.y - autoScrollInsets.top,
autoScrollInsets.left + autoScrollInsets.right,
autoScrollInsets.top + autoScrollInsets.bottom);
scrollRectToVisible(rect);
}
}
/**
Drag and Drop support.
*/
private void _dragOver(DropTargetDragEvent e) {
if (!getAllowDrop()) return;
if (!e.isDataFlavorSupported(OATransferable.HUB_FLAVOR)) return;
Point pt = e.getLocation();
int row = getRowForLocation(pt.x, pt.y);
Hub dragHub = null;
Object dragObject = null;
DropTargetDropEvent temp = null;
try {
Transferable t = e.getTransferable();
dragObject = t.getTransferData(OATransferable.OAOBJECT_FLAVOR);
dragHub = (Hub) t.getTransferData(OATransferable.HUB_FLAVOR);
}
catch (Exception ex) {
System.out.println("OATree.dragOver "+ex);
ex.printStackTrace();
}
if (row < 0) return;
if (dragObject == null) return;
TreePath tp = getPathForRow(row);
Object[] objs = tp.getPath();
if (objs.length < 1) return;
OATreeNodeData tnd = (OATreeNodeData) objs[objs.length-1];
OATreeNode node = tnd.node;
Rectangle rect = getRowBounds(row);
int position = 0; // 0:above 1:below 2:middle
if (node.def.treeNodeChildren.length == 0) {
if (pt.y < (rect.y + (rect.height/2))) position = 0;
else position = 1;
}
else {
if ( (pt.y <= (rect.y + 4)) ) position = 0;
else if (pt.y >= (rect.y + rect.height - 4) ) position = 1;
else position = 2;
}
if (position == 1) {
TreePath tp2 = getPathForRow(row+1);
if (tp2 == null) position = 2;
}
if ( position == 0 ) {
if (row > 0) {
// check node above
TreePath tp2 = getPathForRow(row-1);
Object[] objs2 = tp2.getPath();
if ( (node.titleFlag || !isExpanded(tp2)) && (objs2.length > 1) ) {
OATreeNodeData tnd2 = (OATreeNodeData) objs2[objs2.length-1];
OATreeNode node2 = tnd2.node;
if (node2.getAllowDrop() && (tnd2.getHub() != dragHub)) {
Object obj = tnd2.getObject();
if ( node2.getAllowDrop(dragHub, dragObject, tnd2.getHub()) ) {
// add to end of node above
dragToHub = tnd2.getHub();
dragToObject = tnd2.getObject();
dragAfter = true;
dragToNode = node2;
}
}
}
}
if (dragToHub == null) {
// check for insert above of this node
Object obj = tnd.getObject();
if (obj != null) {
if (node.getAllowDrop() || tnd.getHub() == dragHub) {
if ( node.getAllowDrop(dragHub, dragObject, tnd.getHub()) ) {
dragToHub = tnd.getHub();
dragToObject = obj;
dragToNode = node;
// insert in this node
}
}
}
}
}
else if (position == 1) {
TreePath tp2 = getPathForRow(row+1);
Object[] objs2 = tp2.getPath();
OATreeNodeData tnd2 = null;
OATreeNode node2 = null;
if (dragObject != null && objs2.length > 1) {
tnd2 = (OATreeNodeData) objs2[objs2.length-1];
node2 = tnd2.node;
}
if (!isExpanded(tp) || tp2 == null) {
// drop after this node
Object obj = tnd.getObject();
Hub h = tnd.getHub();
if (obj != null) {
// same class
if (node.getAllowDrop() || (h == dragHub)) {
if ( node.getAllowDrop(dragHub, dragObject, tnd.getHub()) ) {
dragToHub = h;
dragToObject = obj;
dragAfter = true;
dragToNode = node;
}
}
}
if (dragToHub == null && node2 != null && !node2.titleFlag && node2.getAllowDrop()) {
if ( node2.getAllowDrop(dragHub, dragObject, tnd2.getHub()) ) {
obj = tnd2.getObject();
if (obj != null) {
// insert before first expanded child node
dragToHub = tnd2.getHub();
dragToObject = tnd2.getObject();
dragToNode = node2;
}
}
}
}
else {
// check first expanded child to insert before
if (dragObject != null && node2 != null) {
if (node2.getAllowDrop() || (tnd2.getHub() == dragHub)) {
if ( node2.getAllowDrop(dragHub, dragObject, tnd2.getHub()) ) {
Object obj = tnd2.getObject();
if (obj != null) {
// insert before first expanded child node
dragToHub = tnd2.getHub();
dragToObject = tnd2.getObject();
dragToNode = node2;
}
}
}
}
}
}
else {
// drop "inside" of node
// check to see if it can be added to a child node
for (int i=0; (dragToHub == null) && i pos1) pos2--;
}
if (pos2 < 0) pos2 = dragHub.getSize()-1;
dragHub.move(pos1, pos2);
// 20091214
OAUndoManager.add(OAUndoableEdit.createUndoableMove(null, dragHub, pos1, pos2));
}
else {
if (HubAddRemoveDelegate.canAdd(dragToHub, dragObject)) { // 2008/04/18
int x = JOptionPane.showOptionDialog(OAJFCUtil.getWindow(OATree.this), "Ok to move?", "Confirmation", 0, JOptionPane.QUESTION_MESSAGE, null, new String[] { "Yes", "No" }, "Yes");
if (x != 0) return;
if (HubRootDelegate.getRootHub(dragHub) != null && dragHub.getAO() == dragObject) { // 2008/04/18
// this will make sure that a recursive root hub will not be changed to share a child hub. Ex: Model -> ObjectGraphs (root) -> ObjectGraphs <== updateHub used for which ever is the active Hub
dragHub.setActiveObject(null);
}
if (dragToObject != null) {
int pos1 = dragToHub.getPos(dragToObject);
if (dragAfter) pos1++;
dragToHub.insert(dragObject, pos1);
// 20091214
OAUndoManager.add(OAUndoableEdit.createUndoableInsert(null, dragHub, dragObject, pos1));
}
else {
if (!dragToHub.contains(dragObject)) {
dragToHub.add(dragObject);
OAUndoManager.add(OAUndoableEdit.createUndoableAdd(null, dragHub, dragObject));
}
}
}
}
bIsSelectingNode = false;
e.dropComplete(true);
dragToHub.setActiveObject(dragObject);
}
catch (UnsupportedFlavorException ex) {
System.out.println("OATree.drop "+ex);
ex.printStackTrace();
}
catch (IOException ex) {
System.out.println("OATree.drop "+ex);
ex.printStackTrace();
}
finally {
bIsSelectingNode = false;
}
}
//END Drag&Drop
/**
Overwritten to know what rows have been manually expanded. This is used by
addNotify, in cases where the tree is not configured so that the row is not
available to be expanded.
*/
public void expandRow(int x) {
super.expandRow(x);
if (!bAddNotify) onAddNotifyExpandRow = x;
}
private boolean bSettingSelectedNode;
public boolean isSettingSelectedNode() {
return bSettingSelectedNode;
}
/**
* This will select a node, given an object or object path.
* If the objects are not from the top of the tree, then the tree will be scanned to find the
* given object(s)
*
* @see #getTreePath(Object...)
*/
public void setSelectedNode(Object... objects) {
try {
bSettingSelectedNode = true;
_setSelectedNode(objects);
}
finally {
bSettingSelectedNode = false;
}
}
private void _setSelectedNode(Object... objects) {
if (objects == null || objects.length == 0) {
return; // might want to deselect all
}
onAddNotifySelectNode = null;
if (!bAddNotify) {
onAddNotifySelectNode = objects;
return;
}
TreePath tp = getTreePath(objects);
if (tp == null) return;
expandPath(tp.getParentPath());
setSelectionPath(tp);
scrollPathToVisible(tp);
}
/**
* Creates a TreePath from the given object(s). Objects can be a complete
* or partial list from the root of tree, and can include OATreeNode or value of node object
*/
public TreePath getTreePath(Object... objects) {
OATreeNodeData nodeData = (OATreeNodeData) getModel().getRoot(); // the "invisible" root
if (objects == null) return new TreePath(nodeData);
ArrayList al = new ArrayList();
int x = objects.length;
if (objects[x-1] instanceof OATreeNodeData) {
OATreeNodeData tnd = (OATreeNodeData) objects[x-1];
for ( ;tnd != null; ) {
al.add(0, tnd);
tnd = tnd.getParent();
}
TreePath tp = new TreePath(al.toArray());
return tp;
}
al.add(nodeData);
for (int i=0; i al, OATreeNodeData fromNodeData, OATreeNode nodeFind, Object objFind) {
return _findNode(al, fromNodeData, nodeFind, objFind, 0);
}
protected boolean _findNode(ArrayList al, OATreeNodeData fromNodeData, OATreeNode nodeFind, Object objFind, final int counter) {
if (counter > 20) {
LOG.warning("counter > 20, will return false");
return false;
}
boolean bFound = false;
for (int j=0; !bFound && j < fromNodeData.getChildCount(); j++) {
OATreeNodeData tnd = fromNodeData.getChild(j);
if (tnd.node == nodeFind && tnd.object == objFind) {
al.add(tnd);
bFound = true;
}
else if (nodeFind == null && tnd.object == objFind) {
al.add(tnd);
bFound = true;
}
}
for (int j=0; !bFound && j < fromNodeData.getChildCount(); j++) {
OATreeNodeData tnd = fromNodeData.getChild(j);
// try others - this is in case the select objects[] are not from the top of tree
int pos = al.size();
bFound = _findNode(al, tnd, nodeFind, objFind, counter+1);
if (bFound) {
al.add(pos, tnd);
}
}
return bFound;
}
public void expandAll() {
if (!bAddNotify) onAddNotifyExpandAll = true;
else expandAll(new TreePath(model.getRoot()), true, false);
}
public void collapseAll() {
expandAll(new TreePath(model.getRoot()), false, true);
}
private void expandAll(TreePath parent, boolean expand, boolean bChildrenOnly) {
OATreeNodeData node = (OATreeNodeData) parent.getLastPathComponent();
if (!expand) {
if (!node.areChildrenLoaded()) return;
}
for (int i=0; i < node.getChildCount(); i++) {
TreePath path = parent.pathByAddingChild(node.getChild(i));
expandAll(path, expand, false);
}
if (!bChildrenOnly) {
// Expansion or collapse must be done bottom-up
if (expand) {
expandPath(parent);
} else {
collapsePath(parent);
}
}
}
// 20110106
/**
* @see #getTreePath(Object...)
*/
public void expandOnly(Object... objects) {
TreePath tp = getTreePath(objects);
collapseAll();
if (tp != null) {
expandPath(tp.getParentPath());
setSelectionPath(tp);
}
}
public void expandOnlySelectedTreeNode() {
expandOnly(getSelectedTreeNodeData());
}
/**
* @see #getTreePath(Object...)
*/
public void expand(Object... objects) {
TreePath tp = getTreePath(objects);
if (tp != null) expandPath(tp);
}
/**
* @see #getTreePath(Object...)
*/
public void collapse(Object... objects) {
TreePath tp = getTreePath(objects);
if (tp != null) collapsePath(tp);
}
/**
* Internally used to know if structure has changed before the tree is added.
*/
public boolean isReady(boolean bStructureChange) {
if (bStructureChange) {
this.bStructureChanged = true;
}
return bAddNotify;
}
/**
Overwritten to allow for handling rows that have been manually expanded.
Sets up listeners for tree expansion events (TreeExpansionListener).
*/
public void addNotify() {
addNotify(true);
}
public void addNotify(boolean bExpandedVersion) {
if (bExpandedVersion) {
bAddNotify = true;
addTreeExpansionListener(this);
addTreeSelectionListener(this);
}
super.addNotify();
if (!bExpandedVersion) return;
if (bStructureChanged) {
if (SwingUtilities.isEventDispatchThread()) {
model.fireTreeStructureChanged();
bStructureChanged = false;
}
else {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
model.fireTreeStructureChanged();
bStructureChanged = false;
}
});
}
}
if (onAddNotifyExpandRow >= 0) {
if (SwingUtilities.isEventDispatchThread()) doExpandRow();
else {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
doExpandRow();
}
});
}
}
if (onAddNotifyExpandAll) {
onAddNotifyExpandAll = false;
if (SwingUtilities.isEventDispatchThread()) expandAll();
else {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
expandAll();
}
});
}
}
if (onAddNotifySelectNode != null) {
if (SwingUtilities.isEventDispatchThread()) doSelectObject();
else {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
doSelectObject();
}
});
}
}
addNotify2();
}
// 20120711 need to set divider if in a splitpane
public void addNotify2() {
Dimension d = this.getPreferredSize();
if (d == null) return;
Container containerLast = null;
for (Container c = this.getParent(); c!=null ; c=c.getParent()) {
if (d == null || !(c instanceof JSplitPane)) {
containerLast = c;
continue;
}
JSplitPane split = (JSplitPane) c;
if (split.getLeftComponent() != containerLast) {
break;
}
int loc = split.getDividerLocation();
if (split.getOrientation() == JSplitPane.HORIZONTAL_SPLIT) {
if (loc < d.width) {
split.setDividerLocation(d.width);
}
}
else {
if (split.getDividerLocation() < d.height) {
split.setDividerLocation(d.height);
}
}
//split.resetToPreferredSizes();
break;
}
}
private void doSelectObject() {
try {
setSelectedNode(onAddNotifySelectNode);
}
catch (Exception ex) {
System.out.println("OATree.addNotify() "+ex);
}
}
private void doExpandRow() {
try {
expandRow(onAddNotifyExpandRow);
onAddNotifyExpandRow = -1;
}
catch (Exception ex) {
System.out.println("OATree.addNotify() "+ex);
}
}
/**
Removes listeners used for expanding rows.
*/
public void removeNotify() {
if (bWaitOnAddNotify) {
bAddNotify = false;
}
removeTreeExpansionListener(this);
removeTreeSelectionListener(this);
super.removeNotify();
}
public int getMouseOverRow() {
return rowLastMouse2;
}
protected void setupRenderer() {
oldRenderer = getCellRenderer();
setCellRenderer( new TreeCellRenderer() {
private int lastCntUpdateUI;
public Component getTreeCellRendererComponent(JTree tree,Object value,boolean selected,boolean expanded,boolean leaf,int row,boolean hasFocus) {
if (oldRenderer == null) {
oldRenderer = getCellRenderer();
if (oldRenderer == null) {
return null;
}
}
JLabel lbl = (JLabel)oldRenderer.getTreeCellRendererComponent(tree,value,selected,expanded,leaf,row, hasFocus);
if (OATree.this.cntUpdateUI != lastCntUpdateUI) {
lbl.updateUI();
lastCntUpdateUI = OATree.this.cntUpdateUI;
}
lbl.setHorizontalTextPosition(JLabel.RIGHT);
lbl.setOpaque(false);
if (value == null) return lbl;
OATreeNode tn = ((OATreeNodeData)value).node;
if (!OATree.this.bUseIcon || !tn.def.bUseIcon) lbl.setIcon(null);
if (tn.def.icon != null) lbl.setIcon(tn.def.icon);
if (tn.def.font != null) {
lbl.setFont(tn.def.font);
}
else {
Font f = lbl.getFont();
if (fontDefault == null) fontDefault = f;
if (fontDefault != null) {
if (f == null || !f.equals(fontDefault)) lbl.setFont(fontDefault);
}
}
if (tn.def.colorBackground != null) lbl.setBackground(tn.def.colorBackground);
if (tn.def.colorForeground != null) lbl.setForeground(tn.def.colorForeground);
Component comp = lbl;
comp = tn.getTreeCellRendererComponent(comp,tree,value,selected,expanded,leaf,row,hasFocus);
OATreeListener[] l = tn.getListeners();
if (l != null) {
for (int i=0; i= 0) {
if (!selected) {
lbl.setBackground(OATable.COLOR_MouseOver);
lbl.setForeground(Color.white);
lbl.setOpaque(true);
}
}
return comp;
}
});
}
public void addListener(OATreeListener l) {
if (vecListener == null) vecListener = new Vector(2,2);
if (!vecListener.contains(l)) vecListener.addElement(l);
}
public void removeListener(OATreeListener l) {
vecListener.removeElement(l);
}
protected OATreeListener[] getListeners() {
if (vecListener == null) return null;
OATreeListener[] l = new OATreeListener[vecListener.size()];
vecListener.copyInto(l);
return l;
}
void setupEditor() {
setCellEditor( new OATreeCellEditor(this) );
setEditable(true);
}
/**
Returns the invisible root node. The children of this node are the real root nodes
for the tree.
*/
public OATreeNode getRoot() {
return root;
}
/**
Add a top Root Node to this tree. Multiple roots nodes can be added.
All root nodes that have objects to display need to have a Hub assigned.
See OATreeNode for examples.
@see #getRoot
@see OATreeNode#add(OATreeNode)
*/
public void add(OATreeNode node) {
if (node.hub == null && !node.titleFlag) {
throw new RuntimeException("OATree.add() node is not a TitleNode and does not have a Hub assigned");
}
root.add(node);
model.fireTreeStructureChanged(root);
}
/**
Same as calling add() to create a root node.
@see #add(OATreeNode)
*/
public void setRoot(OATreeNode node) {
add(node);
}
/** Tree listener support */
public void treeExpanded(TreeExpansionEvent e) {
}
/** Tree listener support */
public void treeCollapsed(TreeExpansionEvent e) {
}
/**
Used to find out if a leaf node is currently selected.
*/
public boolean isLeafSelected() {
int i = lastSelection.length;
if (i == 0) return false;
return getModel().isLeaf(lastSelection[i-1]) || getModel().getChildCount(lastSelection[i-1]) == 0;
}
public Object[] getLastSelection() {
return lastSelection;
}
/**
Get a string array of treePath of selected node.
*/
public String[] getSelectionAsString() {
Vector vec = new Vector(3,3);
for (int i=1; i < lastSelection.length; i++) {
OATreeNodeData d = (OATreeNodeData) lastSelection[i];
if (!d.node.titleFlag) vec.addElement(d.toString());
}
String[] s = new String[vec.size()];
vec.copyInto(s);
return s;
}
protected boolean isValidSelection(TreeSelectionEvent e) {
return true;
}
private volatile int valueChangeCounter;
/**
Calls nodeSelected(e) method, nodeSelected(node) and objectSelected()
Note: do not overwrite this method, use "nodeSelected(e)" instead.
@see #nodeSelected(TreeSelectionEvent)
@see #nodeSelected(OATreeNode)
@see #objectSelected(Object)
*/
public @Override void valueChanged(final TreeSelectionEvent e) {
if (e == null) return;
if (bIsSettingNode) return;
if (bSettingSelectedNode) return;
final int cnt = ++valueChangeCounter;
if (!isValidSelection(e)) return;
bValueChangedCalled = true; // flag used by mouseEvent to know if valueChanged is called.
// 20170805 does not need to run in swingworker, since the beforeObjectSelected method could be doing UI work
// and any server call to get data is now handled in background thread
TreePath tp = e.getNewLeadSelectionPath();
if (tp != null) {
Object[] objs = tp.getPath();
OATreeNodeData tnd = (OATreeNodeData) objs[objs.length-1];
tnd.node.beforeObjectSelected(tnd.object);
}
try {
_valueChanged(e);
}
catch (Exception t) {
System.out.println("OATree.valueChanged.done exception="+t+" ... will ignore");
t.printStackTrace();
}
/*was
// 20110131 add swingworker
SwingWorker sw = new SwingWorker() {
@Override
protected Void doInBackground() throws Exception {
try {
_doInBackground();
}
catch (Exception t) {
System.out.println("OATree.valueChanged.doInBackground exception="+t);
throw t;
}
return null;
}
protected Void _doInBackground() throws Exception {
if (cnt != valueChangeCounter) return null;
TreePath tp = e.getNewLeadSelectionPath();
if (tp != null) {
Object[] objs = tp.getPath();
OATreeNodeData tnd = (OATreeNodeData) objs[objs.length-1];
tnd.node.beforeObjectSelected(tnd.object);
}
return null;
}
@Override
protected void done() {
if (cnt == valueChangeCounter) { // this does not need to be sync, since AWT thread is used in both places that check counter
try {
_valueChanged(e);
}
catch (Exception t) {
System.out.println("OATree.valueChanged.done exception="+t+" ... will ignore");
t.printStackTrace();
}
}
}
};
sw.execute();
*/
}
private void _valueChanged(TreeSelectionEvent e) {
TreePath tp = e.getNewLeadSelectionPath();
Object[] objs = null;
OATreeNodeData tnd = null;
OATreeNode tn = null;
if (tp != null) {
objs = tp.getPath();
tnd = (OATreeNodeData) objs[objs.length-1];
tn = tnd.node;
}
try {
bIsSelectingNode = true; // block Hub2TreeNode from having hubAO change generating a tree event
nodeSelected(e); // updates node hubs and activeObjects
}
finally {
bIsSelectingNode = false;
}
if (tp == null) {
nodeSelected((OATreeNodeData)null);
}
else {
nodeSelected(tnd);
tn.nodeSelected(tnd);
OATreeListener[] l;
l = getListeners();
if (l != null) {
for (int i=0; i
This is called by valueChanged()
@see #valueChanged(TreeSelectionEvent)
@see #objectSelected
@see #nodeSelected(TreeSelectionEvent)
*/
public Component getTreeCellRendererComponent(Component comp, JTree tree,Object value,boolean selected,boolean expanded,boolean leaf,int row,boolean hasFocus) {
return comp;
}
/**
This will get called when a node is selected/reselected directly from tree.
This can be overwritten to "know" when a node is selected.
This is called by valueChanged()
@see #valueChanged(TreeSelectionEvent)
@see #objectSelected
@see #nodeSelected(TreeSelectionEvent)
@see #add
*/
public void nodeSelected(OATreeNodeData tnd) {
if (tnd == null) nodeSelected((OATreeNode)null);
else nodeSelected(tnd.node);
}
/**
This will get called when a node is selected/reselected directly from tree.
This can be overwritten to "know" when a node is selected.
This is called by nodeSelected(OATreeNodeData)
*/
public void nodeSelected(OATreeNode node) {
}
/**
This will get called when a node is selected/reselected directly from tree.
This can be overwritten to "know" when a node is selected.
This is called by valueChanged()
@see #valueChanged(TreeSelectionEvent)
@see #objectSelected
@see #nodeSelected(OATreeNodeData)
*/
public void objectSelected(Object obj) {
}
/**
This will get called when a node double clicked
This can be overwritten to "know" when a node is double clicked.
This is called by valueChanged()
@see #valueChanged(TreeSelectionEvent)
@see #objectSelected
@see #nodeSelected(OATreeNodeData)
*/
public void onDoubleClick(OATreeNode node, Object object, MouseEvent e) {
}
protected void processFocusEvent(FocusEvent e) {
super.processFocusEvent(e);
}
protected @Override void processMouseMotionEvent(MouseEvent e) {
this._processMouseMotionEvent(e);
Point pt = e.getPoint();
int row = this.getRowForLocation(pt.x, pt.y);
if (row == rowLastMouse2) return;
rowLastMouse2 = row;
if (recLastMouse2 != null) this.repaint(recLastMouse2);
TreePath tp = getPathForRow(row);
if (tp != null) {
Object[] objs = tp.getPath();
OATreeNodeData tnd = (OATreeNodeData) objs[objs.length-1];
OATreeNode node = tnd.node;
recLastMouse2 = getRowBounds(row);
rowLastMouse2 = row;
if (recLastMouse2 != null) {
this.repaint(recLastMouse2);
}
}
else {
recLastMouse2 = null;
row = -1;
}
}
protected void _processMouseMotionEvent(MouseEvent e) {
super.processMouseMotionEvent(e);
downLastMouse = false;
updateCheckBox(e.getPoint());
}
private int rowLastMouse2 = -1;
private Rectangle recLastMouse2;
@Override
protected void processMouseEvent(MouseEvent e) {
this._processMouseEvent(e);
if (e.getID() == MouseEvent.MOUSE_EXITED) {
if (recLastMouse2 != null) {
rowLastMouse2 = -1;
this.repaint(recLastMouse2);
recLastMouse2 = null;
}
}
}
/**
Handles popup menus for TreeNodes.
*/
protected void _processMouseEvent(MouseEvent mouseEvent) {
if (mouseEvent.getID() == MouseEvent.MOUSE_PRESSED) {
bValueChangedCalled = false; // this is used with valueChanged() to know if super.processMouse() calls valueChanged()
downLastMouse = true;
OATreeNodeData tnd = updateCheckBox(mouseEvent.getPoint());
if (tnd != null) { // checkbox was clicked
tnd.node.checkBoxClicked(tnd);
mouseEvent.consume();
return;
}
}
else downLastMouse = false;
boolean bDoubleClicked = false;
// 20101230 need to triple-click node that is not a leaf
if (mouseEvent.getID() == MouseEvent.MOUSE_PRESSED && !bValueChangedCalled) {
if (!mouseEvent.isPopupTrigger()) {
int cc = mouseEvent.getClickCount();
if (cc > 1) {
int[] rows = getSelectionRows();
if (rows != null && rows.length != 0) {
if (isLeafSelected()) {
bDoubleClicked = (cc == 2);
}
else {
bDoubleClicked = (cc == 3);
}
}
}
}
}
try {
super.processMouseEvent(mouseEvent);
}
catch (Exception e) {
LOG.log(Level.WARNING, "", e);
}
TreePath tp = getPathForLocation(mouseEvent.getX(), mouseEvent.getY());
if (mouseEvent.getID() == MouseEvent.MOUSE_PRESSED && !bValueChangedCalled) {
OATree.this.setSelectionRow(getRowForPath(tp));
if (tp != null) {
TreeSelectionEvent tse = new TreeSelectionEvent(OATree.this, tp, false, tp, tp);
valueChanged(tse);
if (bDoubleClicked) {
if (tp != null) {
Object[] objs = tp.getPath();
if (objs != null && objs.length > 0) {
OATreeNodeData tnd = (OATreeNodeData) objs[objs.length-1];
tnd.node.onDoubleClick(tnd.getObject(), mouseEvent);
this.onDoubleClick(tnd.node, tnd.getObject(), mouseEvent);
OATreeListener[] l = tnd.node.getListeners();
if (l != null) {
for (int i=0; i 0) {
OATreeNodeData tnd = (OATreeNodeData) objs[objs.length-1];
tnd.node.popup(mouseEvent.getPoint());
}
}
}
}
}
protected int rowLastMouse;
protected int xLastMouse; // within row editor
protected Rectangle recLastMouse;
protected boolean downLastMouse;
protected OATreeNodeData updateCheckBox(Point pt) {
// 20101215 added try/catch - getRowForLocation is throw npe
for (int i=0; i<3; i++) {
try {
return _updateCheckBox(pt);
}
catch (Exception e) {
System.err.println("OATree.updateCheckBox - caught exception, will retry 3 times. ex="+e);
e.printStackTrace();
}
}
return null;
}
// 2008/04/23
private OATreeNodeData _updateCheckBox(Point pt) {
if (pt == null) return null;
int rowHold = rowLastMouse;
Rectangle recHold = recLastMouse;
rowLastMouse = this.getRowForLocation(pt.x, pt.y);
// check the node
TreePath tp = getPathForRow(rowLastMouse);
if (tp == null) {
if (recHold != null) this.repaint(recHold);
return null;
}
Object[] objs = tp.getPath();
OATreeNodeData tnd = (OATreeNodeData) objs[objs.length-1];
OATreeNode node = tnd.node;
downLastMouse = false;
if (rowHold != rowLastMouse && recHold != null) this.repaint(recHold);
if (!node.needsCheckBox()) return null;
if (rowLastMouse < 0) return null;
recLastMouse = getRowBounds(rowLastMouse);
if (recLastMouse != null) {
xLastMouse = pt.x - recLastMouse.x;
this.repaint(recLastMouse);
int w = node.checkIcon == null ? 20 : node.checkIcon.getIconWidth();
if (xLastMouse > 0 && xLastMouse < w) return tnd;
}
return null;
}
/**
This will get called when a node is selected/reselected directly from tree.
This can be overwritten to "know" when a node is selected.
This is called by valueChanged()
Also can be done by using the OATreeNode.addListener()
Example:
public void nodeSelected(TreeSelectionEvent e) {
super.nodeSelected(e);
TreePath tp = e.getNewLeadSelectionPath();
if (tp == null) return;
Object[] objects = tp.getPath(); // OATreeNodeData objects
if (objects.length == 0) return;
OATreeNodeData tnd = (OATreeNodeData) objects[objects.length-1];
Hub h = tnd.getHub();
if (h != null) {
Class c = h.getObjectClass();
if (c.equals(User.class)) // do something
else if (c.equals(Species.class)) // do something
}
else {
OATreeNode tn = tnd.getNode();
if (tn instanceof OATreeTitleNode) {
OATreeTitleNode n = (OATreeTitleNode) tn;
String title = n.getTitle();
if (title.equals(USER_TABNAME)) // do something
else if (title.equals(SPECIES_TABNAME)) // do something
}
}
}
@see #valueChanged(TreeSelectionEvent)
@see #nodeSelected(OATreeNodeData)
@see #objectSelected(Object)
*/
public void nodeSelected(TreeSelectionEvent e) {
if (e == null) return;
TreePath tp = e.getNewLeadSelectionPath();
if (tp == null) return;
int row = getRowForPath(tp);
if (row < 0) return;
int[] rows = this.getSelectionRows();
if (rows == null) return;
if (rows.length != 1 || rows[0] != row) this.setSelectionRow(row);
lastSelection = tp.getPath();
if (lastSelection == null) return;
HashSet hsUpdateHub = new HashSet(); // hubs that get updated
for (int i = 1; i hsHub) {
if (tnd == null || !tnd.getAreChildrenLoaded()) return;
int x = tnd.getChildCount();
for (int ii=0; ii hsTreeNode, HashSet hsHub) {
if (hsTreeNode == null) hsTreeNode = new HashSet(7);
hsTreeNode.add(tn);
Hub h = tn.def.updateHub;
if (h == null) h = tn.hub;
for (int i=0; tn.def.treeNodeChildren != null && i < tn.def.treeNodeChildren.length; i++) {
if (hsTreeNode.contains(tn.def.treeNodeChildren[i])) continue;
h = tn.def.treeNodeChildren[i].def.updateHub;
if (h == null) h = tn.def.treeNodeChildren[i].hub;
if (h != null && h.getAO() != null && !hsHub.contains(h)) {
hsHub.add(h);
// 20111008 dont set to null if it is a Hub used by a LinkOne detail
OALinkInfo li = HubDetailDelegate.getLinkInfoFromDetailToMaster(h);
if (li != null) {
li = OAObjectInfoDelegate.getReverseLinkInfo(li);
}
if (li == null || li.getType() == OALinkInfo.MANY) {
h.setActiveObject(null);
}
}
if (!hsTreeNode.contains(tn.def.treeNodeChildren[i])) {
clearChildNodeAO(tn.def.treeNodeChildren[i], hsTreeNode, hsHub);
}
}
}
public void setPreferredSize(int rows, int cols) {
this.rows = rows;
this.columns = cols;
if (this.rows > 0) setVisibleRowCount(this.rows);
if (this.columns > 0) {
Dimension d = super.getPreferredSize();
this.widthColumn = OATable.getCharWidth(this,getFont(), cols);
}
invalidate();
}
public void setMinimumSize(int rows, int cols) {
this.miniRows = rows;
this.miniColumns = cols;
if (this.miniColumns > 0) {
Dimension d = super.getMinimumSize();
this.miniWidthColumn = OATable.getCharWidth(this,getFont(), cols);
}
invalidate();
}
@Override
public Dimension getMinimumSize() {
Dimension d = super.getMinimumSize();
if (miniWidthColumn > 0) {
d.width = miniWidthColumn;
}
if (d.height == 0) {
d.height = 10;
}
return d;
}
@Override
public Dimension getMaximumSize() {
Dimension d = super.getMaximumSize();
return d;
}
private Dimension dimLastPerferredSize;
public Dimension getPreferredSize() {
Dimension d = null;
try {
d = super.getPreferredSize();
}
catch (Exception e) {
d = null;
}
if (d == null) {
if (dimLastPerferredSize == null) dimLastPerferredSize = new Dimension(20,20);
d = dimLastPerferredSize;
}
else {
if (widthColumn > 0) {
d.width = widthColumn;
}
if (d.height == 0) {
d.height = 10;
}
dimLastPerferredSize = d;
}
return d;
}
@Override
public void paint(Graphics g) {
try {
super.paint(g);
}
catch (Exception e) {
}
}
/**
Preferred width of tree, based on average width of the font's character.
*/
public void setColumns(int cols) {
setPreferredSize(this.rows, cols);
}
/**
Preferred width of tree, based on average width of the font's character.
*/
public int getColumns() {
return columns;
}
public void setRows(int rows) {
setPreferredSize(rows, this.columns);
}
public int getRows() {
return rows;
}
/**
Refreshes tree, collapsing all nodes.
*/
public void refresh() {
model.fireTreeStructureChanged();
}
private int cntUpdateUI;
public @Override void updateUI() {
super.updateUI();
if (root != null) root.updateUICalled();
cntUpdateUI++;
}
}