org.wings.STree Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2000,2005 wingS development team.
*
* This file is part of wingS (http://wingsframework.org).
*
* wingS 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; either version 2.1
* of the License, or (at your option) any later version.
*
* Please see COPYING for the complete licence.
*/
package org.wings;
import java.awt.Rectangle;
import java.awt.datatransfer.Transferable;
import java.util.*;
import javax.swing.event.EventListenerList;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.tree.AbstractLayoutCache;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.ExpandVetoException;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import javax.swing.tree.VariableHeightLayoutCache;
import javax.swing.*;
import org.wings.event.SMouseEvent;
import org.wings.event.SMouseListener;
import org.wings.event.SViewportChangeEvent;
import org.wings.event.SViewportChangeListener;
import org.wings.plaf.TreeCG;
import org.wings.tree.SDefaultTreeSelectionModel;
import org.wings.tree.STreeCellRenderer;
import org.wings.tree.STreeSelectionModel;
import org.wings.sdnd.TextAndHTMLTransferable;
import org.wings.sdnd.CustomDragHandler;
import org.wings.sdnd.CustomDropStayHandler;
import org.wings.sdnd.SDropMode;
/**
* Swing-like tree widget.
*
* @author Armin Haaf
*/
public class STree extends SComponent implements Scrollable, LowLevelEventListener {
/**
* Tree selection model.
* @see STreeSelectionModel#setSelectionMode(int)
* @see TreeSelectionModel#SINGLE_TREE_SELECTION
*/
public static final int SINGLE_TREE_SELECTION = TreeSelectionModel.SINGLE_TREE_SELECTION;
/**
* Tree selection model.
* @see STreeSelectionModel#setSelectionMode(int)
* @see TreeSelectionModel#CONTIGUOUS_TREE_SELECTION
*/
public static final int CONTIGUOUS_TREE_SELECTION = TreeSelectionModel.CONTIGUOUS_TREE_SELECTION;
/**
* Tree selection model.
* @see STreeSelectionModel#setSelectionMode(int)
* @see TreeSelectionModel#DISCONTIGUOUS_TREE_SELECTION
*/
public static final int DISCONTIGUOUS_TREE_SELECTION = TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION;
/**
* Indent depth in pixels
*/
private int nodeIndentDepth = 20;
private String[] lowLevelEvents;
/**
* Creates and returns a sample TreeModel. Used primarily for beanbuilders.
* to show something interesting.
*
* @return the default TreeModel
*/
protected static TreeModel getDefaultTreeModel() {
DefaultMutableTreeNode root = new DefaultMutableTreeNode("STree");
DefaultMutableTreeNode parent = new DefaultMutableTreeNode("colors");
root.add(parent);
parent.add(new DefaultMutableTreeNode("blue"));
parent.add(new DefaultMutableTreeNode("violet"));
parent.add(new DefaultMutableTreeNode("red"));
parent.add(new DefaultMutableTreeNode("yellow"));
parent = new DefaultMutableTreeNode("sports");
root.add(parent);
parent.add(new DefaultMutableTreeNode("basketball"));
parent.add(new DefaultMutableTreeNode("soccer"));
parent.add(new DefaultMutableTreeNode("football"));
parent.add(new DefaultMutableTreeNode("hockey"));
parent = new DefaultMutableTreeNode("food");
root.add(parent);
parent.add(new DefaultMutableTreeNode("hot dogs"));
parent.add(new DefaultMutableTreeNode("pizza"));
parent.add(new DefaultMutableTreeNode("ravioli"));
parent.add(new DefaultMutableTreeNode("bananas"));
return new DefaultTreeModel(root);
}
protected TreeModel model;
transient protected TreeModelListener treeModelListener;
protected STreeCellRenderer renderer;
protected STreeSelectionModel selectionModel;
/**
* store here all delayed expansion events
*/
private ArrayList delayedExpansionEvents;
/**
* store here expansion paths that will be processed after procession the
* request.
*/
protected final ArrayList requestedExpansionPaths = new ArrayList();
protected transient AbstractLayoutCache treeState = new VariableHeightLayoutCache();
/**
* Implementation of the {@link Scrollable} interface.
*/
protected Rectangle viewport;
/** @see LowLevelEventListener#isEpochCheckEnabled() */
protected boolean epochCheckEnabled = true;
/**
* used to forward selection events to selection listeners of the tree
*/
private final TreeSelectionListener fwdSelectionEvents = e -> {
fireTreeSelectionEvent(e);
if (isUpdatePossible() && STree.class.isAssignableFrom(STree.this.getClass())) {
TreePath[] affectedPaths = e.getPaths();
List deselectedRows = new ArrayList();
List selectedRows = new ArrayList();
for (TreePath affectedPath : affectedPaths) {
int row = treeState.getRowForPath(affectedPath);
if (row == -1)
continue;
int visibleRow = row;
if (viewport != null) {
visibleRow = row - viewport.y;
if (visibleRow < 0 || visibleRow >= viewport.height)
continue;
}
if (e.isAddedPath(affectedPath)) {
selectedRows.add(visibleRow);
} else {
deselectedRows.add(visibleRow);
}
}
update(((TreeCG) getCG()).getSelectionUpdate(STree.this, deselectedRows, selectedRows));
} else {
reload();
}
};
public STree(TreeModel model) {
super();
setModel(model);
setRootVisible(true);
setSelectionModel(new SDefaultTreeSelectionModel());
installTransferHandler();
createActionMap();
}
public STree() {
this(getDefaultTreeModel());
}
public void addTreeSelectionListener(TreeSelectionListener tsl) {
addEventListener(TreeSelectionListener.class, tsl);
}
public void removeTreeSelectionListener(TreeSelectionListener tsl) {
removeEventListener(TreeSelectionListener.class, tsl);
}
protected void fireTreeSelectionEvent(TreeSelectionEvent e) {
// Guaranteed to return a non-null array
Object[] listeners = getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == TreeSelectionListener.class) {
((TreeSelectionListener) listeners[i + 1]).valueChanged(e);
}
}
}
/**
* Adds a listener for TreeWillExpand
events.
*
* @param tel a TreeWillExpandListener
that will be notified
* when a tree node will be expanded or collapsed (a "negative
* expansion")
*/
public void addTreeWillExpandListener(TreeWillExpandListener tel) {
addEventListener(TreeWillExpandListener.class, tel);
}
/**
* Removes a listener for TreeWillExpand
events.
*
* @param tel the TreeWillExpandListener
to remove
*/
public void removeTreeWillExpandListener(TreeWillExpandListener tel) {
removeEventListener(TreeWillExpandListener.class, tel);
}
/**
* Notifies all listeners that have registered interest for
* notification on this event type. The event instance
* is lazily created using the path
parameter.
*
* @param path the TreePath
indicating the node that was
* expanded
* @see EventListenerList
*/
public void fireTreeWillExpand(TreePath path, boolean expand)
throws ExpandVetoException {
// Guaranteed to return a non-null array
Object[] listeners = getListenerList();
TreeExpansionEvent e = null;
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == TreeWillExpandListener.class) {
// Lazily create the event:
if (e == null)
e = new TreeExpansionEvent(this, path);
if (expand) {
((TreeWillExpandListener) listeners[i + 1]).
treeWillExpand(e);
} else {
((TreeWillExpandListener) listeners[i + 1]).
treeWillCollapse(e);
}
}
}
}
public void addTreeExpansionListener(TreeExpansionListener tel) {
addEventListener(TreeExpansionListener.class, tel);
}
public void removeTreeExpansionListener(TreeExpansionListener tel) {
removeEventListener(TreeExpansionListener.class, tel);
}
private static class DelayedExpansionEvent {
TreeExpansionEvent expansionEvent;
boolean expansion;
DelayedExpansionEvent(TreeExpansionEvent e, boolean b) {
expansionEvent = e;
expansion = b;
}
}
protected void addDelayedExpansionEvent(TreeExpansionEvent e,
boolean expansion) {
if (delayedExpansionEvents == null) {
delayedExpansionEvents = new ArrayList();
}
delayedExpansionEvents.add(new DelayedExpansionEvent(e, expansion));
}
protected void fireDelayedExpansionEvents() {
if (delayedExpansionEvents != null &&
!selectionModel.getDelayEvents()) {
for (Object delayedExpansionEvent : delayedExpansionEvents) {
DelayedExpansionEvent e =
(DelayedExpansionEvent) delayedExpansionEvent;
fireTreeExpansionEvent(e.expansionEvent, e.expansion);
}
delayedExpansionEvents.clear();
}
}
/**
* Notify all listeners that have registered interest for
* notification on this event type. The event instance
* is lazily created using the parameters passed into
* the fire method.
*
* @param path the TreePath indicating the node that was expanded
* @see EventListenerList
*/
public void fireTreeExpanded(TreePath path) {
fireTreeExpansionEvent(new TreeExpansionEvent(this, path), true);
}
protected void fireTreeExpansionEvent(TreeExpansionEvent e, boolean expansion) {
if (selectionModel.getDelayEvents()) {
addDelayedExpansionEvent(e, expansion);
} else {
// Guaranteed to return a non-null array
Object[] listeners = getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == TreeExpansionListener.class) {
if (expansion)
((TreeExpansionListener) listeners[i + 1]).treeExpanded(e);
else
((TreeExpansionListener) listeners[i + 1]).treeCollapsed(e);
}
}
fireViewportChanged(false);
}
}
/**
* Notify all listeners that have registered interest for
* notification on this event type. The event instance
* is lazily created using the parameters passed into
* the fire method.
*
* @param path the TreePath indicating the node that was collapsed
* @see EventListenerList
*/
public void fireTreeCollapsed(TreePath path) {
fireTreeExpansionEvent(new TreeExpansionEvent(this, path), false);
}
/**
* Adds the specified mouse listener to receive mouse events from
* this component.
* If l is null, no exception is thrown and no action is performed.
*
* @param l the component listener.
* @see org.wings.event.SMouseEvent
* @see org.wings.event.SMouseListener
* @see org.wings.STable#removeMouseListener
*/
public final void addMouseListener(SMouseListener l) {
addEventListener(SMouseListener.class, l);
}
/**
* Removes the specified mouse listener so that it no longer
* receives mouse events from this component. This method performs
* no function, nor does it throw an exception, if the listener
* specified by the argument was not previously added to this component.
* If l is null, no exception is thrown and no action is performed.
*
* @param l the component listener.
* @see org.wings.event.SMouseEvent
* @see org.wings.event.SMouseListener
* @see org.wings.STable#addMouseListener
*/
public final void removeMouseListener(SMouseListener l) {
removeEventListener(SMouseListener.class, l);
}
/**
* Reports a mouse click event.
*
* @param event report this event to all listeners
* @see org.wings.event.SMouseListener
*/
protected void fireMouseClickedEvent(SMouseEvent event) {
Object[] listeners = getListenerList();
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == SMouseListener.class) {
((SMouseListener)listeners[i + 1]).mouseClicked(event);
}
}
}
protected void processRequestedExpansionPaths() {
selectionModel.setDelayEvents(true);
for (Object requestedExpansionPath : requestedExpansionPaths) {
try {
TreePath path = (TreePath) requestedExpansionPath;
togglePathExpansion(path);
} catch (ExpandVetoException ex) {
// do not expand...
}
}
requestedExpansionPaths.clear();
selectionModel.setDelayEvents(false);
}
private int lastSelectedRow;
protected void addSelectionEvent(int row, boolean ctrlKey, boolean shiftKey) {
TreePath path = getPathForRow(row);
if (path != null) {
if(!shiftKey && !ctrlKey) {
selectionModel.clearSelection();
togglePathSelection(path);
lastSelectedRow = row;
} else if(ctrlKey && !shiftKey) {
togglePathSelection(path);
lastSelectedRow = row;
} else if(!ctrlKey && shiftKey) {
int start = lastSelectedRow;
int end = row;
if(start > end) {
int temp = end;
end = start;
start = temp;
}
selectionModel.clearSelection();
for(int temp=start; temp<=end; ++temp) {
selectionModel.addSelectionPath(getPathForRow(temp));
}
}
}
}
@Override
public void fireIntermediateEvents() {
selectionModel.setDelayEvents(true);
for (String values : lowLevelEvents) {
if (values.length() < 2) continue; // incorrect format
String[] params = values.split(";");
boolean ctrlKey = false;
boolean shiftKey = false;
for (int j = 1; j < params.length; ++j) {
String[] tempVals = params[j].split("=");
if ("ctrlKey".equals(tempVals[0])) {
ctrlKey = Boolean.parseBoolean(tempVals[1]);
} else if ("shiftKey".equals(tempVals[0])) {
shiftKey = Boolean.parseBoolean(tempVals[1]);
}
}
String value = params[0];
SPoint point = new SPoint(value.substring(1));
int row = getRowForLocation(point);
if (row < 0) continue; // row not found...
TreePath path;
switch (value.charAt(0)) {
case 'b':
SMouseEvent event = new SMouseEvent(this, 0, point);
fireMouseClickedEvent(event);
if (event.isConsumed())
continue;
addSelectionEvent(row, ctrlKey, shiftKey);
break;
case 'a':
event = new SMouseEvent(this, 0, point);
fireMouseClickedEvent(event);
if (event.isConsumed())
continue;
path = getPathForAbsoluteRow(row);
if (path != null) {
togglePathSelection(path);
}
break;
case 'h':
path = getPathForRow(row);
if (path != null) {
requestedExpansionPaths.add(path);
}
break;
case 'j':
path = getPathForAbsoluteRow(row);
//selection
if (path != null) {
requestedExpansionPaths.add(path);
}
break;
}
}
selectionModel.setDelayEvents(false);
processRequestedExpansionPaths();
selectionModel.fireDelayedIntermediateEvents();
}
/**
* Returns the table row for the passed SPoint
* instance received via {@link #addMouseListener(org.wings.event.SMouseListener)}.
* @param point The pointed retuned by the mouse event.
* @return The row index
*/
public static int rowAtPoint(SPoint point) {
return getRowForLocation(point);
}
/**
* Returns the tree row for the passed SPoint
.
* instance received via {@link #addMouseListener(org.wings.event.SMouseListener)}.
* @param point The pointed returned by the mouse event.
* @return The tree row index
*/
public static int getRowForLocation(SPoint point) {
return Integer.parseInt(point.getCoordinates());
}
@Override
public void fireFinalEvents() {
super.fireFinalEvents();
fireDelayedExpansionEvents();
selectionModel.fireDelayedFinalEvents();
}
/** @see LowLevelEventListener#isEpochCheckEnabled() */
@Override
public boolean isEpochCheckEnabled() {
return epochCheckEnabled;
}
/** @see LowLevelEventListener#isEpochCheckEnabled() */
public void setEpochCheckEnabled(boolean epochCheckEnabled) {
boolean oldVal = this.epochCheckEnabled;
this.epochCheckEnabled = epochCheckEnabled;
propertyChangeSupport.firePropertyChange("epochCheckEnabled", oldVal, this.epochCheckEnabled);
}
public void setRootVisible(boolean rootVisible) {
if (isRootVisible() != rootVisible) {
boolean oldVal = treeState.isRootVisible();
treeState.setRootVisible(rootVisible);
fireViewportChanged(false);
reload();
propertyChangeSupport.firePropertyChange("rootVisible", oldVal, treeState.isRootVisible());
}
}
public boolean isRootVisible() {
return treeState.isRootVisible();
}
public void setModel(TreeModel m) {
TreeModel oldVal = this.model;
if (model != null && treeModelListener != null)
model.removeTreeModelListener(treeModelListener);
model = m;
treeState.setModel(m);
if (model != null) {
if (treeModelListener == null)
treeModelListener = createTreeModelListener();
if (treeModelListener != null)
model.addTreeModelListener(treeModelListener);
// Mark the root as expanded, if it isn't a leaf.
if (!model.isLeaf(model.getRoot()))
treeState.setExpandedState(new TreePath(model.getRoot()), true);
fireViewportChanged(false);
reload();
}
propertyChangeSupport.firePropertyChange("model", oldVal, this.model);
}
public TreeModel getModel() {
return model;
}
public int getRowCount() {
return treeState.getRowCount();
}
public TreePath getPathForRow(int row) {
return treeState.getPathForRow(row);
}
public int getRowForPath(TreePath path) {
return treeState.getRowForPath(path);
}
protected int fillPathForAbsoluteRow(int row, Object node, ArrayList path) {
// and check if it is the
if (row == 0) {
return 0;
} // end of if ()
for (int i = 0; i < model.getChildCount(node); i++) {
path.add(model.getChild(node, i));
row = fillPathForAbsoluteRow(row - 1, model.getChild(node, i), path);
if (row == 0) {
return 0;
} // end of if ()
path.remove(path.size() - 1);
}
return row;
}
public TreePath getPathForAbsoluteRow(int row) {
// fill path in this array
ArrayList path = new ArrayList(10);
path.add(model.getRoot());
fillPathForAbsoluteRow(row, model.getRoot(), path);
return new TreePath(path.toArray());
}
/**
* Sets the tree's selection model. When a null value is specified
* an empty electionModel is used, which does not allow selections.
*
* @param selectionModel the TreeSelectionModel to use, or null to
* disable selections
* @see TreeSelectionModel
*/
public void setSelectionModel(STreeSelectionModel selectionModel) {
STreeSelectionModel oldVal = this.selectionModel;
if (this.selectionModel != null)
this.selectionModel.removeTreeSelectionListener(fwdSelectionEvents);
if (selectionModel != null && selectionModel != SDefaultTreeSelectionModel.NO_SELECTION_MODEL)
selectionModel.addTreeSelectionListener(fwdSelectionEvents);
if (selectionModel == null)
this.selectionModel = SDefaultTreeSelectionModel.NO_SELECTION_MODEL;
else
this.selectionModel = selectionModel;
propertyChangeSupport.firePropertyChange("selectionModel", oldVal, this.selectionModel);
}
/**
* Returns the model for selections. This should always return a
* non-null value. If you don't want to allow anything to be selected
* set the selection model to null, which forces an empty
* selection model to be used.
*
* @see #setSelectionModel
*/
public STreeSelectionModel getSelectionModel() {
return selectionModel;
}
/**
* Returns JTreePath instances representing the path between index0
* and index1 (including index1).
*
* @param index0 an int specifying a display row, where 0 is the
* first row in the display
* @param index1 an int specifying a second display row
* @return an array of TreePath objects, one for each node between
* index0 and index1, inclusive
*/
protected TreePath[] getPathBetweenRows(int index0, int index1) {
int newMinIndex = Math.min(index0, index1);
int newMaxIndex = Math.max(index0, index1);
TreePath[] selection = new TreePath[newMaxIndex - newMinIndex + 1];
for (int i = newMinIndex; i <= newMaxIndex; i++) {
selection[i - newMinIndex] = getPathForRow(i);
}
return selection;
}
/**
* Selects the node identified by the specified path. If any
* component of the path is hidden (under a collapsed node), it is
* exposed (made viewable).
*
* @param path the TreePath specifying the node to select
*/
public void setSelectionPath(TreePath path) {
TreePath oldVal = selectionModel.getSelectionPath();
selectionModel.setSelectionPath(path);
propertyChangeSupport.firePropertyChange("selectionPath", oldVal, selectionModel.getSelectionPath());
}
/**
* Selects the nodes identified by the specified array of paths.
* If any component in any of the paths is hidden (under a collapsed
* node), it is exposed (made viewable).
*
* @param paths an array of TreePath objects that specifies the nodes
* to select
*/
public void setSelectionPaths(TreePath... paths) {
TreePath[] oldVal = selectionModel.getSelectionPaths();
selectionModel.setSelectionPaths(paths);
propertyChangeSupport.firePropertyChange("selectionPaths", oldVal, selectionModel.getSelectionPaths());
}
/**
* Selects the node at the specified row in the display.
*
* @param row the row to select, where 0 is the first row in
* the display
*/
public void setSelectionRow(int row) {
int[] rows = {row};
setSelectionRows(rows);
}
/**
* Selects the nodes corresponding to each of the specified rows
* in the display.
*
* @param rows an array of ints specifying the rows to select,
* where 0 indicates the first row in the display
*/
public void setSelectionRows(int... rows) {
if (rows == null)
return;
TreePath paths[] = new TreePath[rows.length];
for (int i = 0; i < rows.length; i++) {
paths[i] = getPathForRow(rows[i]);
}
setSelectionPaths(paths);
}
/**
* Adds the node identified by the specified TreePath to the current
* selection. If any component of the path isn't visible, it is
* made visible.
*
* @param path the TreePath to add
*/
public void addSelectionPath(TreePath path) {
selectionModel.addSelectionPath(path);
}
/**
* Adds each path in the array of paths to the current selection. If
* any component of any of the paths isn't visible, it is
* made visible.
*
* @param paths an array of TreePath objects that specifies the nodes
* to add
*/
public void addSelectionPaths(TreePath... paths) {
selectionModel.addSelectionPaths(paths);
}
/**
* Adds the path at the specified row to the current selection.
*
* @param row an int specifying the row of the node to add,
* where 0 is the first row in the display
*/
public void addSelectionRow(int row) {
int[] rows = {row};
addSelectionRows(rows);
}
/**
* Adds the paths at each of the specified rows to the current selection.
*
* @param rows an array of ints specifying the rows to add,
* where 0 indicates the first row in the display
*/
public void addSelectionRows(int... rows) {
if (rows != null) {
int numRows = rows.length;
TreePath[] paths = new TreePath[numRows];
for (int counter = 0; counter < numRows; counter++)
paths[counter] = getPathForRow(rows[counter]);
addSelectionPaths(paths);
}
}
/**
* Returns the last path component in the first node of the current
* selection.
*
* @return the last Object in the first selected node's TreePath,
* or null if nothing is selected
* @see TreePath#getLastPathComponent
*/
public Object getLastSelectedPathComponent() {
Object obj = null;
TreePath selPath = selectionModel.getSelectionPath();
if (selPath != null) {
obj = selPath.getLastPathComponent();
}
return obj;
}
/**
* Returns the path to the first selected node.
*
* @return the TreePath for the first selected node, or null if
* nothing is currently selected
*/
public TreePath getSelectionPath() {
return selectionModel.getSelectionPath();
}
/**
* Returns the paths of all selected values.
*
* @return an array of TreePath objects indicating the selected
* nodes, or null if nothing is currently selected.
*/
public TreePath[] getSelectionPaths() {
return selectionModel.getSelectionPaths();
}
/**
* Returns all of the currently selected rows.
*
* @return an array of ints that identifies all currently selected rows
* where 0 is the first row in the display
*/
public int[] getSelectionRows() {
return selectionModel.getSelectionRows();
}
/**
* Returns the number of nodes selected.
*
* @return the number of nodes selected
*/
public int getSelectionCount() {
return selectionModel.getSelectionCount();
}
/**
* Gets the first selected row.
*
* @return an int designating the first selected row, where 0 is the
* first row in the display
*/
public int getMinSelectionRow() {
return selectionModel.getMinSelectionRow();
}
/**
* Gets the last selected row.
*
* @return an int designating the last selected row, where 0 is the
* first row in the display
*/
public int getMaxSelectionRow() {
return selectionModel.getMaxSelectionRow();
}
/**
* Returns the row index of the last node added to the selection.
*
* @return an int giving the row index of the last node added to the
* selection, where 0 is the first row in the display
*/
public int getLeadSelectionRow() {
return selectionModel.getLeadSelectionRow();
}
/**
* Returns the path of the last node added to the selection.
*
* @return the TreePath of the last node added to the selection.
*/
public TreePath getLeadSelectionPath() {
return selectionModel.getLeadSelectionPath();
}
/**
* Returns true if the item identified by the path is currently selected.
*
* @param path a TreePath identifying a node
* @return true if the node is selected
*/
public boolean isPathSelected(TreePath path) {
return selectionModel.isPathSelected(path);
}
/**
* Returns true if the node identitifed by row is selected.
*
* @param row an int specifying a display row, where 0 is the first
* row in the display
* @return true if the node is selected
*/
public boolean isRowSelected(int row) {
return selectionModel.isRowSelected(row);
}
/**
* Removes the nodes between index0 and index1, inclusive, from the
* selection.
*
* @param index0 an int specifying a display row, where 0 is the
* first row in the display
* @param index1 an int specifying a second display row
*/
public void removeSelectionInterval(int index0, int index1) {
TreePath[] paths = getPathBetweenRows(index0, index1);
selectionModel.removeSelectionPaths(paths);
}
/**
* Removes the node identified by the specified path from the current
* selection.
*
* @param path the TreePath identifying a node
*/
public void removeSelectionPath(TreePath path) {
selectionModel.removeSelectionPath(path);
}
/**
* Removes the nodes identified by the specified paths from the
* current selection.
*
* @param paths an array of TreePath objects that specifies the nodes
* to remove
*/
public void removeSelectionPaths(TreePath... paths) {
selectionModel.removeSelectionPaths(paths);
}
/**
* Removes the path at the index row
from the current
* selection.
*
* @param row the row identifying the node to remove
*/
public void removeSelectionRow(int row) {
int[] rows = {row};
removeSelectionRows(rows);
}
public void removeSelectionRows(int... rows) {
TreePath[] paths = new TreePath[rows.length];
for (int i = 0; i < rows.length; i++)
paths[i] = getPathForRow(rows[i]);
removeSelectionPaths(paths);
}
public int getMaximumExpandedDepth() {
int max = 0;
for (int i = 0; i < getRowCount(); i++)
max = Math.max(max, getPathForRow(i).getPathCount());
return max;
}
/**
* Expand this tree row.
* If tree is inside a {@link SScrollPane} try to
* adjust pane, so that as much as possible new
* nodes are visible.
* @param p the TreePath to expand
* @deprecated This method is deprecated and should not be used because
* expandPath(TreePath) is the proper method with the same functionality.
*/
@Deprecated
public void expandRow(TreePath p) {
expandPath(p);
}
/**
* Expand this tree row.
* If tree is inside a {@link SScrollPane} try to
* adjust pane, so that as much as possible new
* nodes are visible.
* @param p the TreePath to expand
*/
public void expandPath(TreePath p) {
treeState.setExpandedState(p, true);
if (viewport != null) {
Rectangle area = new Rectangle(viewport);
area.y = treeState.getRowForPath(p);
area.height = model.getChildCount(p.getLastPathComponent()) + 1;
scrollRectToVisible(area);
}
fireTreeExpanded(p);
reload();
}
public void expandRow(int row) {
expandPath(getPathForRow(row));
}
/**
* Collapse this tree row.
* If tree is inside a {@link SScrollPane} try to
* adjust pane, so that as much as possible new
* nodes are visible.
* @param p the TreePath to expand
* @deprecated This method is deprecated and should not be used because
* collapsePath(TreePath) is the proper method with the same functionality.
*/
@Deprecated
public void collapseRow(TreePath p) {
collapsePath(p);
}
public void collapsePath(TreePath p) {
treeState.setExpandedState(p, false);
fireTreeCollapsed(p);
reload();
}
public void collapseRow(int row) {
collapseRow(getPathForRow(row));
}
public boolean isVisible(TreePath path) {
if (path != null) {
TreePath parentPath = path.getParentPath();
if (parentPath != null)
return isExpanded(parentPath);
// Root.
return true;
}
return false;
}
public boolean isExpanded(TreePath path) {
return treeState.isExpanded(path);
}
protected void togglePathSelection(TreePath path) {
if (path != null) {
if (isPathSelected(path)) {
removeSelectionPath(path);
} else {
addSelectionPath(path);
}
}
}
protected void togglePathExpansion(TreePath path)
throws ExpandVetoException {
if (path != null) {
if (treeState.isExpanded(path)) {
fireTreeWillExpand(path, false);
collapseRow(path);
} else {
fireTreeWillExpand(path, true);
expandRow(path);
}
}
}
/**
* This is for plafs only!
* With this parameter the tree expands the given node
*/
public static String getExpansionParameter(int row, boolean absolute) {
return (absolute ? "j" : "h") + row;
}
/**
* This is for plafs only!
* With this parameter the tree selects the given node
*/
public static String getSelectionParameter(int row, boolean absolute) {
return (absolute ? "a" : "b") + row;
}
@Override
public void processLowLevelEvent(String action, String... values) {
processKeyEvents(values);
if (action.endsWith("_keystroke"))
return;
this.lowLevelEvents = values;
SForm.addArmedComponent(this);
}
/**
* Set the indent depth in pixel between two nodes of a different level.
* Note: only positive values apply, negative values are cut off at 0.
* @param depth the depth to set
*/
public void setNodeIndentDepth(int depth) {
if (depth < 0) {
depth = 0;
}
if (nodeIndentDepth != depth) {
int oldVal = this.nodeIndentDepth;
nodeIndentDepth = depth;
reload();
propertyChangeSupport.firePropertyChange("nodeIndentDepth", oldVal, this.nodeIndentDepth);
}
}
public int getNodeIndentDepth() {
return nodeIndentDepth;
}
public void setCellRenderer(STreeCellRenderer x) {
STreeCellRenderer oldVal = this.renderer;
renderer = x;
propertyChangeSupport.firePropertyChange("renderer", oldVal, this.renderer);
}
public STreeCellRenderer getCellRenderer() {
return renderer;
}
/**
* Creates an instance of TreeModelHandler.
*/
protected TreeModelListener createTreeModelListener() {
return new TreeModelHandler();
}
/**
* Listens to the model and updates the expandedState accordingly
* when nodes are removed, or changed.
*/
protected class TreeModelHandler implements TreeModelListener {
@Override
public void treeNodesChanged(TreeModelEvent e) {
if (e == null)
return;
treeState.treeNodesChanged(e);
reload();
}
@Override
public void treeNodesInserted(TreeModelEvent e) {
if (e == null)
return;
treeState.treeNodesInserted(e);
fireViewportChanged(false);
reload();
}
@Override
public void treeStructureChanged(TreeModelEvent e) {
if (e == null)
return;
treeState.treeStructureChanged(e);
fireViewportChanged(false);
reload();
}
@Override
public void treeNodesRemoved(TreeModelEvent e) {
if (e == null)
return;
treeState.treeNodesRemoved(e);
fireViewportChanged(false);
reload();
}
}
@Override
public void setParent(SContainer p) {
super.setParent(p);
if (cellRendererPane != null) {
cellRendererPane.setParent(p);
}
}
@Override
protected void setParentFrame(SFrame f) {
super.setParentFrame(f);
if (cellRendererPane != null) {
cellRendererPane.setParentFrame(f);
}
}
// do not initalize with null!
private SCellRendererPane cellRendererPane = new SCellRendererPane();
public SCellRendererPane getCellRendererPane() {
return cellRendererPane;
}
public void setCG(TreeCG cg) {
super.setCG(cg);
}
/**
* The size of the component in respect to scrollable units.
*/
@Override
public Rectangle getScrollableViewportSize() {
return new Rectangle(0, 0, 1, getRowCount());
}
/**
* Returns the actual visible part of a scrollable.
*/
@Override
public Rectangle getViewportSize() {
return viewport;
}
/**
* Sets the actual visible part of a scrollable.
*/
@Override
public void setViewportSize(Rectangle newViewport) {
Rectangle oldViewport = viewport;
viewport = newViewport;
if (isDifferent(oldViewport, newViewport)) {
if (oldViewport == null || newViewport == null) {
fireViewportChanged(true);
fireViewportChanged(false);
} else {
if (newViewport.x != oldViewport.x || newViewport.width != oldViewport.width) {
fireViewportChanged(true);
}
if (newViewport.y != oldViewport.y || newViewport.height != oldViewport.height) {
fireViewportChanged(false);
}
}
reload();
}
propertyChangeSupport.firePropertyChange("viewortSize", oldViewport, this.viewport);
}
/**
* Adds the given SViewportChangeListener
to the scrollable.
*
* @param l the listener to be added
*/
@Override
public void addViewportChangeListener(SViewportChangeListener l) {
addEventListener(SViewportChangeListener.class, l);
}
/**
* Removes the given SViewportChangeListener
from the scrollable.
*
* @param l the listener to be removed
*/
@Override
public void removeViewportChangeListener(SViewportChangeListener l) {
removeEventListener(SViewportChangeListener.class, l);
}
/**
* Notifies all listeners that have registered interest for notification
* on changes to this scrollable's viewport in the specified direction.
*
* @see EventListenerList
*/
protected void fireViewportChanged(boolean horizontal) {
Object[] listeners = getListenerList();
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == SViewportChangeListener.class) {
SViewportChangeEvent event = new SViewportChangeEvent(this, horizontal);
((SViewportChangeListener) listeners[i + 1]).viewportChanged(event);
}
}
}
/**
* Drag and Drop stuff
*/
private SDropMode dropMode = null;
private boolean dragEnabled = false;
protected void createActionMap() {
ActionMap map = getActionMap();
map.put(STransferHandler.getCutAction().getValue(Action.NAME), STransferHandler.getCutAction());
map.put(STransferHandler.getCopyAction().getValue(Action.NAME), STransferHandler.getCopyAction());
map.put(STransferHandler.getPasteAction().getValue(Action.NAME), STransferHandler.getPasteAction());
}
public static final class DropLocation extends STransferHandler.DropLocation {
private int row = -1;
private TreePath path = null;
public DropLocation(STree tree, SPoint point) {
super(point);
try {
row = Integer.parseInt(point.getCoordinates());
path = tree.getPathForRow(row);
} catch(Exception e) {
}
}
public int getRow() {
return row;
}
public TreePath getPath() {
return path;
}
}
public void setDropMode(SDropMode dropMode) {
this.dropMode = dropMode;
getSession().getSDragAndDropManager().addDropTarget(this);
}
public SDropMode getDropMode() {
return this.dropMode;
}
@Override
protected DropLocation dropLocationForPoint(SPoint p) {
if(p.getCoordinates() == null)
return null;
return new STree.DropLocation(this, p);
}
private void installTransferHandler() {
if(getTransferHandler() == null) {
setTransferHandler(new DefaultTransferHandler());
}
}
public void setDragEnabled(boolean dragEnabled) {
if(selectionModel == null && dragEnabled == true)
throw new IllegalStateException("Unable to enable DND - no selection mode set in " + this);
if(dragEnabled != this.dragEnabled) {
if(dragEnabled) {
this.getSession().getSDragAndDropManager().addDragSource(this);
} else {
this.getSession().getSDragAndDropManager().removeDragSource(this);
}
this.dragEnabled = dragEnabled;
}
}
public static class DefaultTransferHandler extends STransferHandler implements Comparator, CustomDragHandler, CustomDropStayHandler {
private STree tree;
public DefaultTransferHandler() {
super(null);
}
@Override
public int compare(TreePath o1, TreePath o2) {
return tree.getRowForPath(o1) - tree.getRowForPath(o2);
}
private TreePath[] getPathsOrdered(TreePath... paths) {
if(paths == null)
return new TreePath[0];
List retPaths = Arrays.asList(paths);
Collections.sort(retPaths, this);
return retPaths.toArray(new TreePath[retPaths.size()]);
}
@Override
protected Transferable createTransferable(SComponent component) {
tree = (STree)component;
String htmlData = "";
String plainTextData = "";
TreePath[] selectedPaths = getPathsOrdered(tree.getSelectionPaths());
for(TreePath path:selectedPaths) {
Object node = path.getLastPathComponent();
plainTextData += node.toString() + '\n';
htmlData += "- " + node.toString() + "
";
}
htmlData += "
";
return new TextAndHTMLTransferable(plainTextData, htmlData);
}
@Override
public int getSourceActions(SComponent component) {
return COPY;
}
@Override
public boolean dragStart(SComponent source, SComponent target, int action, SMouseEvent event) {
try {
String[] coords = event.getPoint().getCoordinates().split(":");
int row = Integer.parseInt(coords[0]);
if(coords.length < 3)
return false;
boolean ctrlKey = false;
boolean shiftKey = false;
for(int i=1; i dropStayConfiguration;
static {
dropStayConfiguration = new HashMap<>();
dropStayConfiguration.put("stayOnElementTimeout", 1500);
}
@Override
public Map getDropStayConfiguration() {
return dropStayConfiguration;
}
}
}