blob: dfc06144d035ee880bbd5bb8a93d35ff35f1018f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 BSI Business Systems Integration AG.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* BSI Business Systems Integration AG - initial API and implementation
******************************************************************************/
package org.eclipse.scout.rt.client.ui.basic.tree;
import java.net.URL;
import java.security.Permission;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EventListener;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import org.eclipse.scout.commons.ConfigurationUtility;
import org.eclipse.scout.commons.EventListenerList;
import org.eclipse.scout.commons.annotations.ConfigOperation;
import org.eclipse.scout.commons.annotations.ConfigProperty;
import org.eclipse.scout.commons.annotations.Order;
import org.eclipse.scout.commons.beans.AbstractPropertyObserver;
import org.eclipse.scout.commons.dnd.TransferObject;
import org.eclipse.scout.commons.exception.ProcessingException;
import org.eclipse.scout.commons.holders.Holder;
import org.eclipse.scout.commons.logger.IScoutLogger;
import org.eclipse.scout.commons.logger.ScoutLogManager;
import org.eclipse.scout.rt.client.ui.IEventHistory;
import org.eclipse.scout.rt.client.ui.action.ActionFinder;
import org.eclipse.scout.rt.client.ui.action.keystroke.IKeyStroke;
import org.eclipse.scout.rt.client.ui.action.keystroke.KeyStroke;
import org.eclipse.scout.rt.client.ui.action.menu.IMenu;
import org.eclipse.scout.rt.client.ui.action.menu.MenuSeparator;
import org.eclipse.scout.rt.client.ui.basic.cell.Cell;
import org.eclipse.scout.rt.client.ui.basic.table.ITableRow;
import org.eclipse.scout.rt.client.ui.profiler.DesktopProfiler;
import org.eclipse.scout.rt.shared.data.form.fields.treefield.AbstractTreeFieldData;
import org.eclipse.scout.rt.shared.data.form.fields.treefield.TreeNodeData;
import org.eclipse.scout.rt.shared.services.common.exceptionhandler.IExceptionHandlerService;
import org.eclipse.scout.rt.shared.services.common.security.IAccessControlService;
import org.eclipse.scout.service.SERVICES;
public abstract class AbstractTree extends AbstractPropertyObserver implements ITree {
private static final IScoutLogger LOG = ScoutLogManager.getLogger(AbstractTree.class);
private final EventListenerList m_listenerList = new EventListenerList();
private ITreeUIFacade m_uiFacade;
private IMenu[] m_menus;
private boolean m_initialized;
// enabled is defined as: enabledGranted && enabledProperty && enabledSlave
private boolean m_enabledGranted;
private boolean m_enabledProperty;
private ITreeNode m_rootNode;
private int m_treeChanging;
private boolean m_autoDiscardOnDelete;
private boolean m_autoTitle;
private final HashMap<Object, ITreeNode> m_deletedNodes;
private ArrayList<TreeEvent> m_treeEventBuffer = new ArrayList<TreeEvent>();
private HashSet<ITreeNode> m_nodeDecorationBuffer = new HashSet<ITreeNode>();
private HashSet<ITreeNode> m_selectedNodes = new HashSet<ITreeNode>();
private final ArrayList<ITreeNodeFilter> m_nodeFilters;
private final int m_uiProcessorCount = 0;
private IKeyStroke[] m_baseKeyStrokes;
private IEventHistory<TreeEvent> m_eventHistory;
// only do one action at a time
private boolean m_actionRunning;
private boolean m_saveAndRestoreScrollbars;
public AbstractTree() {
this(true);
}
public AbstractTree(boolean callInitialzier) {
if (DesktopProfiler.getInstance().isEnabled()) {
DesktopProfiler.getInstance().registerTree(this);
}
m_deletedNodes = new HashMap<Object, ITreeNode>();
m_nodeFilters = new ArrayList<ITreeNodeFilter>(1);
m_actionRunning = false;
if (callInitialzier) {
callInitializer();
}
}
protected void callInitializer() {
if (!m_initialized) {
initConfig();
m_initialized = true;
}
}
/*
* Configuration
*/
@ConfigProperty(ConfigProperty.TEXT)
@Order(10)
protected String getConfiguredTitle() {
return null;
}
@ConfigProperty(ConfigProperty.ICON_ID)
@Order(20)
protected String getConfiguredIconId() {
return null;
}
@ConfigProperty(ConfigProperty.BOOLEAN)
@Order(30)
protected boolean getConfiguredAutoTitle() {
return false;
}
@ConfigProperty(ConfigProperty.BOOLEAN)
@Order(40)
protected boolean getConfiguredMultiSelect() {
return false;
}
@ConfigProperty(ConfigProperty.BOOLEAN)
@Order(42)
protected boolean getConfiguredMultiCheck() {
return true;
}
@ConfigProperty(ConfigProperty.BOOLEAN)
@Order(45)
protected boolean getConfiguredCheckable() {
return false;
}
@ConfigProperty(ConfigProperty.INTEGER)
@Order(46)
protected int getConfiguredNodeHeightHint() {
return -1;
}
@ConfigProperty(ConfigProperty.BOOLEAN)
@Order(50)
protected boolean getConfiguredDragEnabled() {
return false;
}
@ConfigProperty(ConfigProperty.DRAG_AND_DROP_TYPE)
@Order(51)
protected int getConfiguredDragType() {
return 0;
}
@ConfigProperty(ConfigProperty.DRAG_AND_DROP_TYPE)
@Order(52)
protected int getConfiguredDropType() {
return 0;
}
/**
* @return true: deleted nodes are automatically erased<br>
* false: deleted nodes are cached for later processing (service
* deletion)
*/
@ConfigProperty(ConfigProperty.BOOLEAN)
@Order(60)
protected boolean getConfiguredAutoDiscardOnDelete() {
return true;
}
@ConfigProperty(ConfigProperty.BOOLEAN)
@Order(70)
protected boolean getConfiguredRootNodeVisible() {
return false;
}
@ConfigProperty(ConfigProperty.BOOLEAN)
@Order(71)
protected boolean getConfiguredRootHandlesVisible() {
return true;
}
/**
* Advices the ui to automatically scroll to the selection
* <p>
* If not used permanent, this feature can also used dynamically at individual occasions using
*
* <pre>
* {@link #scrollToSelection()}
* </pre>
*/
@ConfigProperty(ConfigProperty.BOOLEAN)
@Order(80)
protected boolean getConfiguredScrollToSelection() {
return false;
}
/**
* Configures whether this tree should save and restore its coordinates of the vertical and horizontal scrollbars.
* If this property is set to {@code true}, the tree saves its scrollbars coordinates to the {@link #ClientSession}
* upon
* detaching the UI component from Scout. The coordinates are restored (if the coordnates are available), when the UI
* component is attached to Scout.
* <p>
* Subclasses can override this method. Default is {@code false}.
*
* @return {@code true} if this tree should save and restore its scrollbars coordinates, {@code false} otherwise
*/
@ConfigProperty(ConfigProperty.BOOLEAN)
@Order(90)
protected boolean getConfiguredSaveAndRestoreScrollbars() {
return false;
}
private Class<? extends IKeyStroke>[] getConfiguredKeyStrokes() {
Class<?>[] dca = ConfigurationUtility.getDeclaredPublicClasses(getClass());
Class<IKeyStroke>[] fca = ConfigurationUtility.filterClasses(dca, IKeyStroke.class);
return ConfigurationUtility.removeReplacedClasses(fca);
}
private Class<? extends IMenu>[] getConfiguredMenus() {
Class<?>[] dca = ConfigurationUtility.getDeclaredPublicClasses(getClass());
Class[] filtered = ConfigurationUtility.filterClasses(dca, IMenu.class);
Class<IMenu>[] foca = ConfigurationUtility.sortFilteredClassesByOrderAnnotation(filtered, IMenu.class);
return ConfigurationUtility.removeReplacedClasses(foca);
}
@ConfigOperation
@Order(10)
protected void execInitTree() throws ProcessingException {
}
@ConfigOperation
@Order(15)
protected void execDisposeTree() throws ProcessingException {
}
/**
* The hyperlink's tree node is the selected node {@link #getSelectedNode()}
*
* @param url
* @param path
* {@link URL#getPath()}
* @param local
* true if the url is not a valid external url but a local model url
* (http://local/...)
*/
@ConfigOperation
@Order(18)
protected void execHyperlinkAction(URL url, String path, boolean local) throws ProcessingException {
}
/**
* this method should not be implemented if you support {@link AbstractTree#execDrag(ITreeNode[])} (drag of mulitple
* nodes), as it takes precedence
*
* @return a transferable object representing the given row
*/
@ConfigOperation
@Order(20)
protected TransferObject execDrag(ITreeNode node) throws ProcessingException {
return null;
}
/**
* Drag of multiple nodes. If this method is implemented, also single drags will be handled by Scout,
* the method {@link AbstractTree#execDrag(ITreeNode)} must not be implemented then.
*
* @return a transferable object representing the given rows
*/
@ConfigOperation
@Order(30)
protected TransferObject execDrag(ITreeNode[] nodes) throws ProcessingException {
return null;
}
/**
* process drop action
*/
@ConfigOperation
@Order(40)
protected void execDrop(ITreeNode node, TransferObject t) throws ProcessingException {
}
/**
* decoration for every cell calls this method
* <p>
* Default delegates to {@link ITreeNode#decorateCell()}
*/
@ConfigOperation
@Order(50)
protected void execDecorateCell(ITreeNode node, Cell cell) throws ProcessingException {
if (cell.getIconId() == null && getIconId() != null) {
cell.setIconId(getIconId());
}
node.decorateCell();
}
@ConfigOperation
@Order(60)
protected void execNodesSelected(TreeEvent e) throws ProcessingException {
}
@ConfigOperation
@Order(70)
protected void execNodeClick(ITreeNode node) throws ProcessingException {
TreeEvent e = new TreeEvent(this, TreeEvent.TYPE_NODE_CLICK, node);
fireTreeEventInternal(e);
}
@ConfigOperation
@Order(80)
protected void execNodeAction(ITreeNode node) throws ProcessingException {
TreeEvent e = new TreeEvent(this, TreeEvent.TYPE_NODE_ACTION, node);
fireTreeEventInternal(e);
}
protected void initConfig() {
m_enabledGranted = true;
m_eventHistory = createEventHistory();
m_uiFacade = new P_UIFacade();
setTitle(getConfiguredTitle());
setIconId(getConfiguredIconId());
setAutoTitle(getConfiguredAutoTitle());
setCheckable(getConfiguredCheckable());
setNodeHeightHint(getConfiguredNodeHeightHint());
setMultiCheck(getConfiguredMultiCheck());
setMultiSelect(getConfiguredMultiSelect());
setAutoDiscardOnDelete(getConfiguredAutoDiscardOnDelete());
setDragEnabled(getConfiguredDragEnabled());
setDragType(getConfiguredDragType());
setDropType(getConfiguredDropType());
setRootNodeVisible(getConfiguredRootNodeVisible());
setRootHandlesVisible(getConfiguredRootHandlesVisible());
setScrollToSelection(getConfiguredScrollToSelection());
setSaveAndRestoreScrollbars(getConfiguredSaveAndRestoreScrollbars());
setRootNode(new AbstractTreeNode() {
});
// add Convenience observer for drag & drop callbacks and event history
addTreeListener(new TreeAdapter() {
@Override
public void treeChanged(TreeEvent e) {
//event history
IEventHistory<TreeEvent> h = getEventHistory();
if (h != null) {
h.notifyEvent(e);
}
//dnd
switch (e.getType()) {
case TreeEvent.TYPE_NODES_DRAG_REQUEST: {
if (e.getDragObject() == null) {
try {
TransferObject transferObject = execDrag(e.getNode());
if (transferObject == null) {
transferObject = execDrag(e.getNodes());
}
e.setDragObject(transferObject);
}
catch (Throwable t) {
LOG.error("Drag", t);
}
}
break;
}
case TreeEvent.TYPE_NODE_DROP_ACTION: {
if (e.getDropObject() != null) {
try {
execDrop(e.getNode(), e.getDropObject());
}
catch (Throwable t) {
LOG.error("Drop", t);
}
}
break;
}
case TreeEvent.TYPE_NODES_SELECTED: {
rebuildKeyStrokesInternal();
break;
}
}
}
});
// key shortcuts
ArrayList<IKeyStroke> ksList = new ArrayList<IKeyStroke>();
Class<? extends IKeyStroke>[] shortcutArray = getConfiguredKeyStrokes();
for (int i = 0; i < shortcutArray.length; i++) {
IKeyStroke ks;
try {
ks = ConfigurationUtility.newInnerInstance(this, shortcutArray[i]);
ksList.add(ks);
}
catch (Throwable t) {
SERVICES.getService(IExceptionHandlerService.class).handleException(new ProcessingException("keyStroke: " + shortcutArray[i].getName(), t));
}
}
//ticket 87370: add ENTER key stroke when execNodeAction has an override
if (ConfigurationUtility.isMethodOverwrite(AbstractTree.class, "execNodeAction", new Class[]{ITreeNode.class}, this.getClass())) {
ksList.add(new KeyStroke("ENTER") {
@Override
protected void execAction() throws ProcessingException {
fireNodeAction(getSelectedNode());
}
});
}
m_baseKeyStrokes = ksList.toArray(new IKeyStroke[ksList.size()]);
setKeyStrokesInternal(m_baseKeyStrokes);
// menus
ArrayList<IMenu> menuList = new ArrayList<IMenu>();
Class<? extends IMenu>[] ma = getConfiguredMenus();
for (int i = 0; i < ma.length; i++) {
try {
IMenu menu = ConfigurationUtility.newInnerInstance(this, ma[i]);
menuList.add(menu);
}
catch (Exception e) {
LOG.error("Exception occured while creating a new instance of " + ma[i].getName(), e);
}
}
try {
injectMenusInternal(menuList);
}
catch (Exception e) {
LOG.error("Error occured while dynamically contributing menus.", e);
}
m_menus = menuList.toArray(new IMenu[0]);
}
/*
* Runtime
*/
@Override
public final void initTree() throws ProcessingException {
initTreeInternal();
execInitTree();
}
protected void initTreeInternal() throws ProcessingException {
}
@Override
public final void disposeTree() {
disposeTreeInternal();
try {
execDisposeTree();
}
catch (Throwable t) {
LOG.warn(getClass().getName(), t);
}
}
protected void disposeTreeInternal() {
}
/**
* Override this internal method only in order to make use of dynamic menus<br/>
* Used to manage menu list and add/remove menus
*
* @param menuList
* live and mutable list of configured menus
*/
protected void injectMenusInternal(List<IMenu> menuList) {
}
@Override
public IMenu[] getMenus() {
return m_menus;
}
@Override
public void setMenus(IMenu[] a) {
m_menus = a;
}
@Override
public <T extends IMenu> T getMenu(Class<T> menuType) throws ProcessingException {
// ActionFinder performs instance-of checks. Hence the menu replacement mapping is not required
return new ActionFinder().findAction(getMenus(), menuType);
}
@Override
public boolean hasNodeFilters() {
return m_nodeFilters.size() > 0;
}
@Override
public ITreeNodeFilter[] getNodeFilters() {
return m_nodeFilters.toArray(new ITreeNodeFilter[m_nodeFilters.size()]);
}
@Override
public void addNodeFilter(ITreeNodeFilter filter) {
if (filter != null) {
//avoid duplicate add
boolean exists = false;
for (ITreeNodeFilter existingFilter : m_nodeFilters) {
if (existingFilter == filter) {
exists = true;
break;
}
}
if (!exists) {
m_nodeFilters.add(filter);
}
applyNodeFilters();
}
}
@Override
public void removeNodeFilter(ITreeNodeFilter filter) {
if (filter != null) {
m_nodeFilters.remove(filter);
applyNodeFilters();
}
}
@Override
public void applyNodeFilters() {
applyNodeFiltersRecInternal(getRootNode(), true, 0);
fireNodeFilterChanged();
}
private void applyNodeFiltersRecInternal(ITreeNode inode, boolean parentAccepted, int level) {
if (inode == null) {
return;
}
inode.setFilterAccepted(true);
if (m_nodeFilters.size() > 0) {
for (ITreeNodeFilter filter : m_nodeFilters) {
if (!filter.accept(inode, level)) {
inode.setFilterAccepted(false);
break;
}
}
}
if (!inode.isFilterAccepted() && isSelectedNode(inode)) {
// invisible nodes cannot be selected
deselectNode(inode);
}
// make parent path accepted
if ((!parentAccepted) && inode.isFilterAccepted()) {
ITreeNode tmp = inode.getParentNode();
while (tmp != null) {
if (tmp instanceof AbstractTreeNode) {
((AbstractTreeNode) tmp).setFilterAccepted(true);
}
tmp = tmp.getParentNode();
}
}
// children
for (ITreeNode child : inode.getChildNodes()) {
applyNodeFiltersRecInternal(child, inode.isFilterAccepted(), level + 1);
}
}
@Override
public void requestFocus() {
fireRequestFocus();
}
@Override
public ITreeNode getRootNode() {
return m_rootNode;
}
@Override
public Object getProperty(String name) {
return propertySupport.getProperty(name);
}
@Override
public void setProperty(String name, Object value) {
propertySupport.setProperty(name, value);
}
@Override
public boolean hasProperty(String name) {
return propertySupport.hasProperty(name);
}
@Override
public String getTitle() {
return propertySupport.getPropertyString(PROP_TITLE);
}
@Override
public void setTitle(String s) {
propertySupport.setPropertyString(PROP_TITLE, s);
}
@Override
public boolean isAutoTitle() {
return m_autoTitle;
}
@Override
public void setAutoTitle(boolean b) {
m_autoTitle = b;
}
@Override
public String getIconId() {
String iconId = propertySupport.getPropertyString(PROP_ICON_ID);
if (iconId != null && iconId.length() == 0) {
iconId = null;
}
return iconId;
}
@Override
public void setIconId(String iconId) {
propertySupport.setPropertyString(PROP_ICON_ID, iconId);
}
@Override
public boolean isCheckable() {
return propertySupport.getPropertyBool(PROP_CHECKABLE);
}
@Override
public void setCheckable(boolean b) {
propertySupport.setPropertyBool(PROP_CHECKABLE, b);
}
@Override
public int getNodeHeightHint() {
return propertySupport.getPropertyInt(PROP_NODE_HEIGHT_HINT);
}
@Override
public void setNodeHeightHint(int h) {
propertySupport.setPropertyInt(PROP_NODE_HEIGHT_HINT, h);
}
@Override
public boolean isDragEnabled() {
return propertySupport.getPropertyBool(PROP_DRAG_ENABLED);
}
@Override
public void setDragEnabled(boolean b) {
propertySupport.setPropertyBool(PROP_DRAG_ENABLED, b);
}
@Override
public void setDragType(int dragType) {
propertySupport.setPropertyInt(PROP_DRAG_TYPE, dragType);
}
@Override
public int getDragType() {
return propertySupport.getPropertyInt(PROP_DRAG_TYPE);
}
@Override
public void setDropType(int dropType) {
propertySupport.setPropertyInt(PROP_DROP_TYPE, dropType);
}
@Override
public int getDropType() {
return propertySupport.getPropertyInt(PROP_DROP_TYPE);
}
@Override
public void setEnabledPermission(Permission p) {
boolean b;
if (p != null) {
b = SERVICES.getService(IAccessControlService.class).checkPermission(p);
}
else {
b = true;
}
setEnabledGranted(b);
}
@Override
public boolean isEnabledGranted() {
return m_enabledGranted;
}
@Override
public void setEnabledGranted(boolean b) {
m_enabledGranted = b;
calculateEnabled();
}
@Override
public void setEnabled(boolean b) {
m_enabledProperty = b;
calculateEnabled();
}
@Override
public boolean isEnabled() {
return propertySupport.getPropertyBool(PROP_ENABLED);
}
private void calculateEnabled() {
propertySupport.setPropertyBool(PROP_ENABLED, m_enabledGranted && m_enabledProperty);
}
@Override
public String getPathText(ITreeNode selectedNode) {
return getPathText(selectedNode, " - ");
}
@Override
public String getPathText(ITreeNode selectedNode, String delimiter) {
// construct the path to the data
ITreeNode root = getRootNode();
StringBuffer pathStr = new StringBuffer("");
ITreeNode node = selectedNode;
while (node != null) {
if (node != root || isRootNodeVisible()) {
if (pathStr.length() != 0) {
pathStr.insert(0, delimiter);
}
pathStr.insert(0, node.getCell().getText());
}
// next
node = node.getParentNode();
}
return pathStr.toString();
}
private void rebuildTitleInternal() {
setTitle(getPathText(getSelectedNode()));
}
private void rebuildKeyStrokesInternal() {
//Get the menus for the selected nodes
IMenu[] menus;
try {
ITreeNode[] nodes = resolveVirtualNodes(getSelectedNodes());
menus = fetchMenusForNodesInternal(nodes);
}
catch (ProcessingException e) {
SERVICES.getService(IExceptionHandlerService.class).handleException(e);
menus = new IMenu[0];
}
//Compute the Keystrokes: base + keyStroke for the current Menus.
ArrayList<IKeyStroke> ksList = new ArrayList<IKeyStroke>(Arrays.asList(m_baseKeyStrokes));
for (IMenu menu : menus) {
if (menu.getKeyStroke() != null) {
IKeyStroke ks = new KeyStroke(menu.getKeyStroke(), menu);
ksList.add(ks);
}
}
//Set KeyStrokes:
setKeyStrokesInternal(ksList.toArray(new IKeyStroke[ksList.size()]));
}
@Override
public ITreeNode findNode(Object primaryKey) {
ITreeNode[] a = findNodes(new Object[]{primaryKey});
if (a != null && a.length > 0) {
return a[0];
}
else {
return null;
}
}
@Override
public ITreeNode[] findNodes(Object[] primaryKeys) {
if (primaryKeys == null || primaryKeys.length <= 0) {
return new ITreeNode[0];
}
final HashSet<Object> keySet = new HashSet<Object>(Arrays.asList(primaryKeys));
P_AbstractCollectingTreeVisitor v = new P_AbstractCollectingTreeVisitor() {
@Override
public boolean visit(ITreeNode node) {
if (keySet.remove(node.getPrimaryKey())) {
addNodeToList(node);
}
return !keySet.isEmpty();
}
};
visitNode(getRootNode(), v);
return v.getNodes();
}
@Override
public void setRootNode(ITreeNode root) {
if (m_rootNode != null) {
m_rootNode.setTreeInternal(null, true);
// inform root of remove
root.nodeRemovedNotify();
}
m_rootNode = root;
if (m_rootNode != null) {
m_rootNode.setTreeInternal(this, true);
// inform root of add
m_rootNode.nodeAddedNotify();
// expand root if it is not visible
if (!isRootNodeVisible()) {
try {
m_rootNode.ensureChildrenLoaded();
}
catch (ProcessingException e) {
LOG.error("expanding root node of " + getTitle(), e);
}
}
}
}
@Override
public boolean isRootNodeVisible() {
return propertySupport.getPropertyBool(PROP_ROOT_NODE_VISIBLE);
}
@Override
public void setRootNodeVisible(boolean b) {
propertySupport.setPropertyBool(PROP_ROOT_NODE_VISIBLE, b);
}
@Override
public boolean isRootHandlesVisible() {
return propertySupport.getPropertyBool(PROP_ROOT_HANDLES_VISIBLE);
}
@Override
public void setRootHandlesVisible(boolean b) {
propertySupport.setPropertyBool(PROP_ROOT_HANDLES_VISIBLE, b);
}
@Override
public boolean isTreeChanging() {
return m_treeChanging > 0;
}
@Override
public void setTreeChanging(boolean b) {
// use a stack counter because setTableChanging might be called in nested
// loops
if (b) {
m_treeChanging++;
if (m_treeChanging == 1) {
// 0 --> 1
propertySupport.setPropertiesChanging(true);
}
}
else {
if (m_treeChanging > 0) {
m_treeChanging--;
if (m_treeChanging == 0) {
try {
processChangeBuffer();
}
finally {
propertySupport.setPropertiesChanging(false);
}
}
}
}
}
@Override
public boolean isNodeExpanded(ITreeNode node) {
if (node != null) {
return node.isExpanded();
}
else {
return false;
}
}
@Override
public void setNodeExpanded(ITreeNode node, boolean b) {
node = resolveNode(node);
if (node != null) {
if (node.isExpanded() != b) {
try {
if (b) {
node.ensureChildrenLoaded();
ensureParentExpanded(node.getParentNode());
}
node.setExpandedInternal(b);
fireNodeExpanded(node, b);
}
catch (ProcessingException e) {
SERVICES.getService(IExceptionHandlerService.class).handleException(e);
}
}
}
}
@Override
public boolean isAncestorNodeOf(ITreeNode parent, ITreeNode child) {
ITreeNode t = child;
while (t != null && t != parent) {
t = t.getParentNode();
}
return t == parent;
}
@Override
public boolean isAutoDiscardOnDelete() {
return m_autoDiscardOnDelete;
}
@Override
public void setAutoDiscardOnDelete(boolean on) {
m_autoDiscardOnDelete = on;
}
@Override
public void setNodeEnabledPermission(ITreeNode node, Permission p) {
node = resolveNode(node);
if (node != null) {
boolean oldValue = node.isEnabled();
node.setEnabledPermissionInternal(p);
boolean newValue = node.isEnabled();
if (oldValue != newValue) {
fireNodesUpdated(node.getParentNode(), new ITreeNode[]{node});
}
}
}
@Override
public boolean isNodeEnabled(ITreeNode node) {
if (node != null) {
return node.isEnabled();
}
else {
return false;
}
}
@Override
public boolean isNodeEnabledGranted(ITreeNode node) {
if (node != null) {
return node.isEnabledGranted();
}
else {
return false;
}
}
@Override
public void setNodeEnabled(ITreeNode node, boolean b) {
node = resolveNode(node);
if (node != null) {
boolean oldValue = node.isEnabled();
node.setEnabledInternal(b);
boolean newValue = node.isEnabled();
if (oldValue != newValue) {
fireNodesUpdated(node.getParentNode(), new ITreeNode[]{node});
}
}
}
@Override
public void setNodeEnabledGranted(ITreeNode node, boolean b) {
node = resolveNode(node);
if (node != null) {
boolean oldValue = node.isEnabled();
node.setEnabledGrantedInternal(b);
boolean newValue = node.isEnabled();
if (oldValue != newValue) {
fireNodesUpdated(node.getParentNode(), new ITreeNode[]{node});
}
}
}
@Override
public void setNodeVisiblePermission(ITreeNode node, Permission p) {
node = resolveNode(node);
if (node != null) {
boolean oldValue = node.isVisible();
node.setVisiblePermissionInternal(p);
boolean newValue = node.isVisible();
if (oldValue != newValue) {
// dont fire observers since visibility change only has an effect when
// used in init method
}
}
}
@Override
public boolean isNodeVisible(ITreeNode node) {
if (node != null) {
return node.isVisible();
}
else {
return false;
}
}
@Override
public boolean isNodeVisibleGranted(ITreeNode node) {
if (node != null) {
return node.isVisibleGranted();
}
else {
return false;
}
}
@Override
public void setNodeVisible(ITreeNode node, boolean b) {
node = resolveNode(node);
if (node != null) {
boolean oldValue = node.isVisible();
node.setVisibleInternal(b);
boolean newValue = node.isVisible();
if (oldValue != newValue) {
// dont fire observers since visibility change only has an effect when
// used in init method
}
}
}
@Override
public void setNodeVisibleGranted(ITreeNode node, boolean b) {
node = resolveNode(node);
if (node != null) {
boolean oldValue = node.isVisible();
node.setVisibleGrantedInternal(b);
boolean newValue = node.isVisible();
if (oldValue != newValue) {
// dont fire observers since visibility change only has an effect when
// used in init method
}
}
}
@Override
public boolean isNodeLeaf(ITreeNode node) {
if (node != null) {
return node.isLeaf();
}
else {
return false;
}
}
@Override
public void setNodeLeaf(ITreeNode node, boolean b) {
node = resolveNode(node);
if (node != null) {
if (node.isLeaf() != b) {
node.setLeafInternal(b);
fireNodesUpdated(node.getParentNode(), new ITreeNode[]{node});
}
}
}
@Override
public boolean isNodeChecked(ITreeNode node) {
if (node != null) {
return node.isChecked();
}
else {
return false;
}
}
@Override
public void setNodeChecked(ITreeNode node, boolean b) {
node = resolveNode(node);
if (node != null) {
if (node.isChecked() != b) {
ArrayList<ITreeNode> changedNodes = new ArrayList<ITreeNode>();
node.setCheckedInternal(b);
changedNodes.add(node);
ITreeNode commonParent = node.getParentNode();
//uncheck others in single-check mode
if (b && !isMultiCheck()) {
for (ITreeNode cn : getCheckedNodes()) {
if (cn != node) {
cn.setCheckedInternal(false);
changedNodes.add(cn);
}
}
commonParent = TreeUtility.findLowestCommonAncestorNode(changedNodes);
}
fireNodesUpdated(commonParent, changedNodes.toArray(new ITreeNode[changedNodes.size()]));
}
}
}
@Override
public int getNodeStatus(ITreeNode node) {
if (node != null) {
return node.getStatus();
}
else {
return ITreeNode.STATUS_NON_CHANGED;
}
}
@Override
public void setNodeStatus(ITreeNode node, int status) {
node = resolveNode(node);
if (node != null) {
if (node.getStatus() != status) {
node.setStatusInternal(status);
fireNodesUpdated(node.getParentNode(), new ITreeNode[]{node});
}
}
}
@Override
public Object getContainer() {
return propertySupport.getProperty(PROP_CONTAINER);
}
/**
* do not use this internal method unless you are implementing a container that holds and controls an {@link ITree}
*/
public void setContainerInternal(Object container) {
propertySupport.setProperty(PROP_CONTAINER, container);
}
private void ensureParentExpanded(ITreeNode parent) {
if (parent != null) {
ensureParentExpanded(parent.getParentNode());
if (!parent.isExpanded()) {
setNodeExpanded(parent, true);
}
}
}
@Override
public void ensureVisible(ITreeNode node) {
fireNodeEnsureVisible(node);
}
@Override
public void expandAll(ITreeNode parent) {
expandAllRec(parent, 0);
}
private void expandAllRec(ITreeNode parent, int level) {
setNodeExpanded(parent, true);
// loop detection
if (level >= 32) {
LOG.warn("detected loop on tree node " + parent);
}
else {
ITreeNode[] children = parent.getChildNodes();
for (int i = 0; i < children.length; i++) {
expandAllRec(children[i], level + 1);
}
}
}
@Override
public void collapseAll(ITreeNode parent) {
try {
setTreeChanging(true);
//
ArrayList<ITreeNode> list = new ArrayList<ITreeNode>();
fetchAllCollapsingNodesRec(parent, 0, list);
for (int n = list.size(), i = n - 1; i >= 0; i--) {
setNodeExpanded(list.get(i), false);
}
}
finally {
setTreeChanging(false);
}
}
private void fetchAllCollapsingNodesRec(ITreeNode parent, int level, List<ITreeNode> list) {
// loop detection
if (level >= 32) {
LOG.warn("detected loop on tree node " + parent);
}
else {
if (parent.isExpanded()) {
list.add(parent);
ITreeNode[] children = parent.getChildNodes();
for (int i = 0; i < children.length; i++) {
fetchAllCollapsingNodesRec(children[i], level + 1, list);
}
}
}
}
@Override
public IKeyStroke[] getKeyStrokes() {
IKeyStroke[] keyStrokes = (IKeyStroke[]) propertySupport.getProperty(PROP_KEY_STROKES);
if (keyStrokes == null) {
keyStrokes = new IKeyStroke[0];
}
return keyStrokes;
}
@Override
public void setKeyStrokes(IKeyStroke[] keyStrokes) {
m_baseKeyStrokes = keyStrokes;
rebuildKeyStrokesInternal();
}
private void setKeyStrokesInternal(IKeyStroke[] keyStrokes) {
propertySupport.setProperty(PROP_KEY_STROKES, keyStrokes);
}
/*
* modifications
*/
@Override
public void addChildNode(ITreeNode parent, ITreeNode child) {
if (child != null) {
addChildNodes(parent, new ITreeNode[]{child});
}
}
@Override
public void addChildNode(int startIndex, ITreeNode parent, ITreeNode child) {
if (child != null) {
addChildNodes(startIndex, parent, new ITreeNode[]{child});
}
}
@Override
public void addChildNodes(ITreeNode parent, ITreeNode[] children) {
addChildNodes(parent.getChildNodeCount(), parent, children);
}
@Override
public void addChildNodes(int startIndex, ITreeNode parent, ITreeNode[] children) {
if (children == null || children.length == 0) {
return;
}
try {
setTreeChanging(true);
//
parent = resolveNode(parent);
((AbstractTreeNode) parent).addChildNodesInternal(startIndex, children, true);
// check if all children were added, or if somem were revoked using
// visible=false in init (addNotify) phase.
int revokeCount = 0;
for (ITreeNode child : children) {
if (child.getParentNode() == null) {
revokeCount++;
}
}
if (revokeCount > 0) {
ITreeNode[] newChildren = new ITreeNode[children.length - revokeCount];
int index = 0;
for (ITreeNode child : children) {
if (child.getParentNode() != null) {
newChildren[index++] = child;
}
}
children = newChildren;
}
// decorate
decorateAffectedNodeCells(parent, children);
// filter
int level = 0;
ITreeNode tmp = parent;
while (tmp != null) {
tmp = tmp.getParentNode();
level++;
}
for (ITreeNode child : children) {
applyNodeFiltersRecInternal(child, parent.isFilterAccepted(), level);
}
fireNodesInserted(parent, children);
}
finally {
setTreeChanging(false);
}
}
@Override
public void updateNode(ITreeNode node) {
updateChildNodes(node.getParentNode(), new ITreeNode[]{node});
}
@Override
public void updateChildNodes(ITreeNode parent, ITreeNode[] children) {
try {
setTreeChanging(true);
//
parent = resolveNode(parent);
children = resolveNodes(children);
decorateAffectedNodeCells(parent, children);
fireNodesUpdated(parent, children);
}
finally {
setTreeChanging(false);
}
}
@Override
public void updateChildNodeOrder(ITreeNode parent, ITreeNode[] newChildren) {
try {
setTreeChanging(true);
//
parent = resolveNode(parent);
ITreeNode[] newChildrenResolved = resolveNodes(newChildren);
if (newChildren.length > 0 && newChildrenResolved.length == newChildren.length) {
((AbstractTreeNode) parent).setChildNodeOrderInternal(newChildrenResolved);
decorateAffectedNodeCells(parent, newChildrenResolved);
fireChildNodeOrderChanged(parent, newChildrenResolved);
}
}
finally {
setTreeChanging(false);
}
}
@Override
public void removeNode(ITreeNode node) {
ITreeNode parent = node.getParentNode();
ITreeNode child = node;
removeChildNode(parent, child);
}
@Override
public void removeChildNode(ITreeNode parent, ITreeNode child) {
removeChildNodes(parent, new ITreeNode[]{child});
}
@Override
public void removeChildNodes(ITreeNode parent, ITreeNode[] children) {
if (children == null || children.length == 0) {
return;
}
try {
setTreeChanging(true);
//
parent = resolveNode(parent);
if (parent == null) {
return;
}
children = resolveNodes(children);
deselectNodes(children);
((AbstractTreeNode) parent).removeChildNodesInternal(children, true);
decorateAffectedNodeCells(parent, parent.getChildNodes());
if (!isAutoDiscardOnDelete()) {
for (int i = 0; i < children.length; i++) {
if (children[i].getStatus() == ITreeNode.STATUS_INSERTED) {
// it was new and now it is gone, no further action required
}
else {
children[i].setStatusInternal(ITableRow.STATUS_DELETED);
m_deletedNodes.put(children[i].getPrimaryKey(), children[i]);
}
}
}
// filter
int level = 0;
ITreeNode tmp = parent;
while (tmp != null) {
tmp = tmp.getParentNode();
level++;
}
for (ITreeNode child : parent.getChildNodes()) {
applyNodeFiltersRecInternal(child, parent.isFilterAccepted(), level);
}
fireNodesDeleted(parent, children);
}
finally {
setTreeChanging(false);
}
}
@Override
public void removeAllChildNodes(ITreeNode parent) {
if (parent != null) {
removeChildNodes(parent, parent.getChildNodes());
}
}
@Override
public void clearDeletedNodes() {
for (Iterator<ITreeNode> it = m_deletedNodes.values().iterator(); it.hasNext();) {
(it.next()).setTreeInternal(null, true);
}
m_deletedNodes.clear();
}
@Override
public ITreeNode[] resolveVirtualNodes(ITreeNode[] nodes) throws ProcessingException {
if (nodes == null) {
return new ITreeNode[0];
}
try {
setTreeChanging(true);
//
ArrayList<ITreeNode> resolvedNodes = new ArrayList<ITreeNode>(nodes.length);
for (int i = 0; i < nodes.length; i++) {
ITreeNode resolvedNode = resolveVirtualNode(nodes[i]);
if (resolvedNode != null) {
resolvedNodes.add(resolvedNode);
}
}
return resolvedNodes.toArray(new ITreeNode[resolvedNodes.size()]);
}
finally {
setTreeChanging(false);
}
}
@Override
public ITreeNode resolveVirtualNode(ITreeNode node) throws ProcessingException {
if (node instanceof IVirtualTreeNode) {
IVirtualTreeNode vnode = (IVirtualTreeNode) node;
if (vnode.getResolvedNode() != null && vnode.getResolvedNode().getTree() == this) {
return vnode.getResolvedNode();
}
if (vnode.getTree() != this) {
return null;
}
ITreeNode parentNode = vnode.getParentNode();
if (parentNode == null) {
return null;
}
try {
setTreeChanging(true);
//
ITreeNode resolvedNode = parentNode.resolveVirtualChildNode(vnode);
if (resolvedNode != vnode && vnode.getResolvedNode() == null) {
vnode.setResolvedNode(resolvedNode);
}
return resolvedNode;
}
finally {
setTreeChanging(false);
}
}
return node;
}
@Override
public boolean visitTree(ITreeVisitor v) {
return visitNodeRec(getRootNode(), v);
}
@Override
public boolean visitNode(ITreeNode node, ITreeVisitor v) {
return visitNodeRec(node, v);
}
private boolean visitNodeRec(ITreeNode node, ITreeVisitor v) {
if (node == null) {
return true;
}
boolean b = v.visit(node);
if (!b) {
return b;
}
ITreeNode[] a = node.getChildNodes();
for (int i = 0; i < a.length; i++) {
// it might be that the visit of a node detached the node from the tree
if (a[i].getTree() != null) {
b = visitNodeRec(a[i], v);
if (!b) {
return b;
}
}
}
return true;
}
@Override
public boolean visitVisibleTree(ITreeVisitor v) {
return visitVisibleNodeRec(getRootNode(), v, isRootNodeVisible());
}
private boolean visitVisibleNodeRec(ITreeNode node, ITreeVisitor v, boolean includeParent) {
if (node.isVisible()) {
if (includeParent) {
boolean b = v.visit(node);
if (!b) {
return b;
}
}
if (node.isExpanded()) {
ITreeNode[] a = node.getFilteredChildNodes();
for (int i = 0; i < a.length; i++) {
// it might be that the visit of a node detached the node from the
// tree
if (a[i].getTree() != null) {
boolean b = visitVisibleNodeRec(a[i], v, true);
if (!b) {
return b;
}
}
}
}
}
return true;
}
@Override
public int getDeletedNodeCount() {
return m_deletedNodes.size();
}
@Override
public ITreeNode[] getDeletedNodes() {
return m_deletedNodes.values().toArray(new ITreeNode[0]);
}
@Override
public int getInsertedNodeCount() {
P_AbstractCountingTreeVisitor v = new P_AbstractCountingTreeVisitor() {
@Override
public boolean visit(ITreeNode node) {
if (node.isStatusInserted()) {
addCount(1);
}
return true;
}
};
visitNode(getRootNode(), v);
return v.getCount();
}
@Override
public ITreeNode[] getInsertedNodes() {
P_AbstractCollectingTreeVisitor v = new P_AbstractCollectingTreeVisitor() {
@Override
public boolean visit(ITreeNode node) {
if (node.isStatusInserted()) {
addNodeToList(node);
}
return true;
}
};
visitNode(getRootNode(), v);
return v.getNodes();
}
@Override
public int getUpdatedNodeCount() {
P_AbstractCountingTreeVisitor v = new P_AbstractCountingTreeVisitor() {
@Override
public boolean visit(ITreeNode node) {
if (node.isStatusUpdated()) {
addCount(1);
}
return true;
}
};
visitNode(getRootNode(), v);
return v.getCount();
}
@Override
public ITreeNode[] getUpdatedNodes() {
P_AbstractCollectingTreeVisitor v = new P_AbstractCollectingTreeVisitor() {
@Override
public boolean visit(ITreeNode node) {
if (node.isStatusUpdated()) {
addNodeToList(node);
}
return true;
}
};
visitNode(getRootNode(), v);
return v.getNodes();
}
@Override
public int getSelectedNodeCount() {
return m_selectedNodes.size();
}
@Override
public ITreeNode getSelectedNode() {
if (m_selectedNodes.size() > 0) {
return m_selectedNodes.iterator().next();
}
else {
return null;
}
}
@Override
public ITreeNode[] getSelectedNodes() {
return m_selectedNodes.toArray(new ITreeNode[0]);
}
@Override
public boolean isSelectedNode(ITreeNode node) {
node = resolveNode(node);
if (node != null) {
return m_selectedNodes.contains(node);
}
else {
return false;
}
}
@Override
public void selectNode(ITreeNode node) {
selectNode(node, false);
}
@Override
public void selectNode(ITreeNode node, boolean append) {
if (node != null) {
selectNodes(new ITreeNode[]{node}, append);
}
else {
selectNodes(new ITreeNode[0], append);
}
}
@Override
public void selectNodes(ITreeNode[] nodes, boolean append) {
nodes = resolveNodes(nodes);
try {
nodes = resolveVirtualNodes(nodes);
}
catch (ProcessingException e) {
LOG.warn("could not resolve virtual nodes.", e);
}
if (nodes == null) {
nodes = new ITreeNode[0];
}
HashSet<ITreeNode> newSelection = new HashSet<ITreeNode>();
if (append) {
newSelection.addAll(m_selectedNodes);
newSelection.addAll(Arrays.asList(nodes));
}
else {
newSelection.addAll(Arrays.asList(nodes));
}
// check selection count with multiselect
if (newSelection.size() > 1 && !isMultiSelect()) {
ITreeNode first = newSelection.iterator().next();
newSelection.clear();
newSelection.add(first);
}
if (m_selectedNodes.equals(newSelection) && m_selectedNodes.containsAll(Arrays.asList(nodes))) {
// ok
}
else {
HashSet<ITreeNode> oldSelection = m_selectedNodes;
fireBeforeNodesSelected(oldSelection, newSelection);
m_selectedNodes = newSelection;
fireNodesSelected(oldSelection, m_selectedNodes);
}
}
@Override
public void selectNextNode() {
final ITreeNode current = getSelectedNode();
if (current != null) {
final Holder<ITreeNode> foundVisited = new Holder<ITreeNode>(ITreeNode.class);
ITreeVisitor v = new ITreeVisitor() {
boolean foundCurrent;
@Override
public boolean visit(ITreeNode node) {
if (foundCurrent) {
if (node.isFilterAccepted()) {
foundVisited.setValue(node);
}
return foundVisited.getValue() == null;
}
else {
if (node == current) {
foundCurrent = true;
}
return true;
}
}
};
visitVisibleTree(v);
if (foundVisited.getValue() != null) {
selectNode(foundVisited.getValue());
}
}
else {
selectFirstNode();
}
}
@Override
public void selectPreviousNode() {
final ITreeNode current = getSelectedNode();
if (current != null) {
final Holder<ITreeNode> foundVisited = new Holder<ITreeNode>(ITreeNode.class);
ITreeVisitor v = new ITreeVisitor() {
boolean foundCurrent;
@Override
public boolean visit(ITreeNode node) {
if (foundCurrent) {
return false;
}
if (node == current) {
foundCurrent = true;
}
else if (node.isFilterAccepted()) {
foundVisited.setValue(node);
}
return true;
}
};
visitVisibleTree(v);
if (foundVisited.getValue() != null) {
selectNode(foundVisited.getValue());
}
}
else {
selectLastNode();
}
}
@Override
public void selectFirstNode() {
if (!isRootNodeVisible()) {
try {
getRootNode().ensureChildrenLoaded();
}
catch (ProcessingException e) {
SERVICES.getService(IExceptionHandlerService.class).handleException(e);
}
}
final Holder<ITreeNode> foundVisited = new Holder<ITreeNode>(ITreeNode.class);
ITreeVisitor v = new ITreeVisitor() {
@Override
public boolean visit(ITreeNode node) {
if (foundVisited.getValue() != null) {
return false;
}
if (node.isFilterAccepted()) {
foundVisited.setValue(node);
}
return true;
}
};
visitVisibleTree(v);
if (foundVisited.getValue() != null) {
selectNode(foundVisited.getValue());
}
}
@Override
public void selectLastNode() {
if (!isRootNodeVisible()) {
try {
getRootNode().ensureChildrenLoaded();
}
catch (ProcessingException e) {
SERVICES.getService(IExceptionHandlerService.class).handleException(e);
}
}
final Holder<ITreeNode> foundVisited = new Holder<ITreeNode>(ITreeNode.class);
ITreeVisitor v = new ITreeVisitor() {
@Override
public boolean visit(ITreeNode node) {
if (node.isFilterAccepted()) {
foundVisited.setValue(node);
}
return true;
}
};
visitVisibleTree(v);
if (foundVisited.getValue() != null) {
selectNode(foundVisited.getValue());
}
}
@Override
public void selectNextChildNode() {
ITreeNode current = getSelectedNode();
if (current != null) {
current.setExpanded(true);
}
selectNextNode();
}
@Override
public void selectPreviousParentNode() {
ITreeNode n = getSelectedNode();
if (n != null) {
ITreeNode parent = n.getParentNode();
while (parent != null) {
if (parent != getRootNode() || isRootNodeVisible()) {
if (parent.isFilterAccepted()) {
selectNode(parent);
return;
}
}
//
parent = parent.getParentNode();
}
}
else {
selectFirstNode();
}
}
@Override
public void deselectNode(ITreeNode node) {
if (node != null) {
deselectNodes(new ITreeNode[]{node});
}
else {
deselectNodes(new ITreeNode[0]);
}
}
@Override
public void deselectNodes(ITreeNode[] nodes) {
nodes = resolveNodes(nodes);
if (nodes != null && nodes.length > 0) {
HashSet<ITreeNode> oldSelection = new HashSet<ITreeNode>(m_selectedNodes);
HashSet<ITreeNode> newSelection = new HashSet<ITreeNode>();
if (m_selectedNodes != null) {
for (ITreeNode selChild : m_selectedNodes) {
boolean accept = true;
for (ITreeNode delParent : nodes) {
if (isAncestorNodeOf(delParent, selChild)) {
accept = false;
break;
}
}
if (accept) {
newSelection.add(selChild);
}
}
}
if (oldSelection.size() != newSelection.size()) {
fireBeforeNodesSelected(oldSelection, newSelection);
m_selectedNodes = newSelection;
fireNodesSelected(oldSelection, m_selectedNodes);
}
}
}
@Override
public ITreeNode[] getCheckedNodes() {
final ArrayList<ITreeNode> list = new ArrayList<ITreeNode>();
visitTree(new ITreeVisitor() {
@Override
public boolean visit(ITreeNode node) {
if (node.isChecked()) {
list.add(node);
}
return true;
}
});
return list.toArray(new ITreeNode[list.size()]);
}
@Override
public boolean isScrollToSelection() {
return propertySupport.getPropertyBool(PROP_SCROLL_TO_SELECTION);
}
@Override
public void setScrollToSelection(boolean b) {
propertySupport.setPropertyBool(PROP_SCROLL_TO_SELECTION, b);
}
@Override
public void scrollToSelection() {
fireTreeEventInternal(new TreeEvent(this, TreeEvent.TYPE_SCROLL_TO_SELECTION));
}
private ITreeNode resolveNode(ITreeNode node) {
if (node instanceof IVirtualTreeNode && ((IVirtualTreeNode) node).getResolvedNode() != null) {
node = ((IVirtualTreeNode) node).getResolvedNode();
}
// unwrapping
if (node == null) {
return null;
}
else if (node.getTree() == this) {
return node;
}
else {
return null;
}
}
private ITreeNode[] resolveNodes(ITreeNode[] nodes) {
if (nodes == null) {
return new ITreeNode[0];
}
int mismatchCount = 0;
for (int i = 0; i < nodes.length; i++) {
if (resolveNode(nodes[i]) == null) {
mismatchCount++;
}
}
if (mismatchCount > 0) {
ITreeNode[] resolvedNodes = new ITreeNode[nodes.length - mismatchCount];
int index = 0;
for (int i = 0; i < nodes.length; i++) {
if (resolveNode(nodes[i]) != null) {
resolvedNodes[index] = nodes[i];
index++;
}
}
nodes = resolvedNodes;
}
return nodes;
}
/*
* Tree Observer
*/
@Override
public void addTreeListener(TreeListener listener) {
m_listenerList.add(TreeListener.class, listener);
}
@Override
public void removeTreeListener(TreeListener listener) {
m_listenerList.remove(TreeListener.class, listener);
}
@Override
public void addUITreeListener(TreeListener listener) {
m_listenerList.insertAtFront(TreeListener.class, listener);
}
protected IEventHistory<TreeEvent> createEventHistory() {
return new DefaultTreeEventHistory(5000L);
}
@Override
public IEventHistory<TreeEvent> getEventHistory() {
return m_eventHistory;
}
private void fireNodesInserted(ITreeNode parent, ITreeNode[] children) {
if (children.length > 0) {
fireTreeEventInternal(new TreeEvent(this, TreeEvent.TYPE_NODES_INSERTED, parent, children));
}
}
private void fireNodesUpdated(ITreeNode parent, ITreeNode[] children) {
if (children.length > 0) {
fireTreeEventInternal(new TreeEvent(this, TreeEvent.TYPE_NODES_UPDATED, parent, children));
}
}
@Override
public void fireNodeChanged(ITreeNode node) {
fireTreeEventInternal(new TreeEvent(this, TreeEvent.TYPE_NODE_CHANGED, node));
}
private void fireNodeFilterChanged() {
fireTreeEventInternal(new TreeEvent(this, TreeEvent.TYPE_NODE_FILTER_CHANGED, getRootNode()));
}
private void fireNodesDeleted(ITreeNode parent, ITreeNode[] children) {
if (children.length > 0) {
fireTreeEventInternal(new TreeEvent(this, TreeEvent.TYPE_NODES_DELETED, parent, children));
}
}
private void fireChildNodeOrderChanged(ITreeNode parent, ITreeNode[] children) {
if (children.length > 0) {
fireTreeEventInternal(new TreeEvent(this, TreeEvent.TYPE_CHILD_NODE_ORDER_CHANGED, parent, children));
}
}
private void fireBeforeNodesSelected(Set<ITreeNode> oldSelection, Set<ITreeNode> newSelection) {
ITreeNode[] nodes = newSelection.toArray(new ITreeNode[newSelection.size()]);
TreeEvent e = new TreeEvent(this, TreeEvent.TYPE_BEFORE_NODES_SELECTED, nodes);
HashSet<ITreeNode> deselectedNodes = new HashSet<ITreeNode>(oldSelection);
deselectedNodes.removeAll(newSelection);
e.setDeselectedNodes(deselectedNodes.toArray(new ITreeNode[deselectedNodes.size()]));
HashSet<ITreeNode> newSelectedNodes = new HashSet<ITreeNode>(newSelection);
newSelectedNodes.removeAll(oldSelection);
e.setNewSelectedNodes(newSelectedNodes.toArray(new ITreeNode[newSelectedNodes.size()]));
fireTreeEventInternal(e);
}
private void fireNodesSelected(Set<ITreeNode> oldSelection, Set<ITreeNode> newSelection) {
// single observer: rebuild title
if (isAutoTitle()) {
rebuildTitleInternal();
}
ITreeNode[] nodes = newSelection.toArray(new ITreeNode[newSelection.size()]);
// fire
TreeEvent e = new TreeEvent(this, TreeEvent.TYPE_NODES_SELECTED, nodes);
HashSet<ITreeNode> deselectedNodes = new HashSet<ITreeNode>(oldSelection);
deselectedNodes.removeAll(newSelection);
e.setDeselectedNodes(deselectedNodes.toArray(new ITreeNode[deselectedNodes.size()]));
HashSet<ITreeNode> newSelectedNodes = new HashSet<ITreeNode>(newSelection);
newSelectedNodes.removeAll(oldSelection);
e.setNewSelectedNodes(newSelectedNodes.toArray(new ITreeNode[newSelectedNodes.size()]));
//single observer
try {
execNodesSelected(e);
}
catch (ProcessingException ex) {
SERVICES.getService(IExceptionHandlerService.class).handleException(ex);
}
catch (Throwable t) {
SERVICES.getService(IExceptionHandlerService.class).handleException(new ProcessingException("Unexpected", t));
}
//end single observer
fireTreeEventInternal(e);
}
private void fireNodeExpanded(ITreeNode node, boolean b) {
if (node != null) {
if (b) {
fireTreeEventInternal(new TreeEvent(this, TreeEvent.TYPE_NODE_EXPANDED, node));
}
else {
fireTreeEventInternal(new TreeEvent(this, TreeEvent.TYPE_NODE_COLLAPSED, node));
}
}
}
@Override
public IMenu[] fetchMenusForNodesInternal(ITreeNode[] nodes) {
TreeEvent e = new TreeEvent(this, TreeEvent.TYPE_NODE_POPUP, nodes);
// single observer for tree-owned menus
addLocalPopupMenus(e);
fireTreeEventInternal(e);
//separate node menus and empty space actions
ArrayList<IMenu> nodeMenus = new ArrayList<IMenu>();
ArrayList<IMenu> emptySpaceMenus = new ArrayList<IMenu>();
for (IMenu menu : e.getPopupMenus()) {
if (menu.isVisible()) {
if (menu.isEmptySpaceAction()) {
emptySpaceMenus.add(menu);
}
else {
nodeMenus.add(menu);
}
}
}
if (nodeMenus.size() > 0 && emptySpaceMenus.size() > 0) {
nodeMenus.add(0, new MenuSeparator());
}
nodeMenus.addAll(0, emptySpaceMenus);
return nodeMenus.toArray(new IMenu[nodeMenus.size()]);
}
private void fireNodeClick(ITreeNode node) {
if (node != null) {
try {
interceptNodeClickSingleObserver(node);
execNodeClick(node);
}
catch (ProcessingException ex) {
SERVICES.getService(IExceptionHandlerService.class).handleException(ex);
}
catch (Throwable t) {
SERVICES.getService(IExceptionHandlerService.class).handleException(new ProcessingException("Unexpected", t));
}
}
}
protected void interceptNodeClickSingleObserver(ITreeNode node) {
if (isCheckable() && node.isEnabled() && isEnabled()) {
node.setChecked(!node.isChecked());
}
}
private void fireNodeAction(ITreeNode node) {
if (!m_actionRunning) {
try {
m_actionRunning = true;
if (node != null) {
if (node.isLeaf()) {
try {
execNodeAction(node);
}
catch (ProcessingException ex) {
SERVICES.getService(IExceptionHandlerService.class).handleException(ex);
}
catch (Throwable t) {
SERVICES.getService(IExceptionHandlerService.class).handleException(new ProcessingException("Unexpected", t));
}
}
}
}
finally {
m_actionRunning = false;
}
}
}
private void fireRequestFocus() {
TreeEvent e = new TreeEvent(this, TreeEvent.TYPE_REQUEST_FOCUS);
fireTreeEventInternal(e);
}
private void addLocalPopupMenus(TreeEvent e) {
int selectionCount = e.getNodes().length;
ArrayList<IMenu> list = new ArrayList<IMenu>();
for (IMenu m : this.getMenus()) {
if ((!m.isInheritAccessibility()) || (isEnabled())) {
m.prepareAction();
if (m.isVisible()) {
list.add(m);
}
}
}
if (e.getNode() != null) {
for (IMenu m : e.getNode().getMenus()) {
if ((!m.isInheritAccessibility()) || (e.getNode().isEnabled() && isEnabled())) {
m.prepareAction();
if (m.isVisible()) {
list.add(m);
}
}
}
}
//check single/multi select
for (IMenu menu : list) {
if (selectionCount > 1 && menu.isMultiSelectionAction()) {
e.addPopupMenu(menu);
}
else if (selectionCount == 1 && menu.isSingleSelectionAction()) {
e.addPopupMenu(menu);
}
else if (selectionCount == 0 && menu.isEmptySpaceAction()) {
e.addPopupMenu(menu);
}
}
}
private TransferObject fireNodesDragRequest(ITreeNode[] nodes) {
if (nodes != null && nodes.length > 0) {
TreeEvent e = new TreeEvent(this, TreeEvent.TYPE_NODES_DRAG_REQUEST, nodes);
fireTreeEventInternal(e);
return e.getDragObject();
}
else {
return null;
}
}
private void fireNodeDropAction(ITreeNode node, TransferObject dropData) {
TreeEvent e = new TreeEvent(this, TreeEvent.TYPE_NODE_DROP_ACTION, node);
e.setDropObject(dropData);
fireTreeEventInternal(e);
}
private void fireNodeRequestFocus(ITreeNode node) {
TreeEvent e = new TreeEvent(this, TreeEvent.TYPE_NODE_REQUEST_FOCUS, node);
fireTreeEventInternal(e);
}
private void fireNodeEnsureVisible(ITreeNode node) {
TreeEvent e = new TreeEvent(this, TreeEvent.TYPE_NODE_ENSURE_VISIBLE, node);
fireTreeEventInternal(e);
}
// main handler
protected void fireTreeEventInternal(TreeEvent e) {
if (isTreeChanging()) {
// buffer the event for later batch firing
m_treeEventBuffer.add(e);
}
else {
EventListener[] listeners = m_listenerList.getListeners(TreeListener.class);
if (listeners != null && listeners.length > 0) {
for (int i = 0; i < listeners.length; i++) {
try {
((TreeListener) listeners[i]).treeChanged(e);
}
catch (Throwable t) {
LOG.error("fire " + e, t);
}
}
}
}
}
// batch handler
private void fireTreeEventBatchInternal(TreeEvent[] batch) {
if (batch.length == 0) {
return;
}
EventListener[] listeners = m_listenerList.getListeners(TreeListener.class);
if (listeners != null && listeners.length > 0) {
for (int i = 0; i < listeners.length; i++) {
((TreeListener) listeners[i]).treeChangedBatch(batch);
}
}
}
/**
* add cells on the path to root and on children to decoration buffer
*/
private void decorateAffectedNodeCells(ITreeNode parent, ITreeNode[] children) {
decorateAffectedNodeCellsOnPathToRoot(parent);
for (ITreeNode child : children) {
decorateAffectedNodeCellsOnSubtree(child);
}
}
private void decorateAffectedNodeCellsOnPathToRoot(ITreeNode node) {
ITreeNode tmp = node;
while (tmp != null) {
m_nodeDecorationBuffer.add(tmp);
tmp = tmp.getParentNode();
}
}
private void decorateAffectedNodeCellsOnSubtree(ITreeNode node) {
m_nodeDecorationBuffer.add(node);
for (ITreeNode child : node.getChildNodes()) {
decorateAffectedNodeCellsOnSubtree(child);
}
}
private int m_processChangeBufferLoopDetection;
/**
* affects columns with lookup calls or code types<br>
* cells that have changed values fetch new texts/decorations from the lookup
* service in one single batch call lookup (performance optimization)
*/
private void processChangeBuffer() {
//loop detection
try {
m_processChangeBufferLoopDetection++;
if (m_processChangeBufferLoopDetection > 100) {
LOG.error("LOOP DETECTION in " + getClass() + ". see stack trace for more details.", new Exception("LOOP DETECTION"));
return;
}
//
/*
* update row decorations
*/
if (m_nodeDecorationBuffer.size() > 0) {
HashSet<ITreeNode> set = m_nodeDecorationBuffer;
m_nodeDecorationBuffer = new HashSet<ITreeNode>();
for (Iterator<ITreeNode> it = set.iterator(); it.hasNext();) {
ITreeNode node = it.next();
if (node.getTree() != null) {
try {
execDecorateCell(node, node.getCellForUpdate());
}
catch (Throwable t) {
LOG.warn("node " + node.getClass() + " " + node.getCell().getText(), t);
}
}
}
}
/*
* fire events tree changes are finished now, fire all buffered events
* and call lookups
*/
if (m_treeEventBuffer.size() > 0) {
ArrayList<TreeEvent> list = m_treeEventBuffer;
m_treeEventBuffer = new ArrayList<TreeEvent>();
// coalesce selection events
boolean foundSelectionEvent = false;
for (ListIterator<TreeEvent> it = list.listIterator(list.size()); it.hasPrevious();) {
if (it.previous().getType() == TreeEvent.TYPE_NODES_SELECTED) {
if (!foundSelectionEvent) {
foundSelectionEvent = true;
}
else {
it.remove();
}
}
}
// fire the batch and set tree to changing, otherwise a listener might trigger another events that then are processed before all other listeners received that batch
try {
setTreeChanging(true);
//
fireTreeEventBatchInternal(list.toArray(new TreeEvent[list.size()]));
}
finally {
setTreeChanging(false);
}
}
}
finally {
m_processChangeBufferLoopDetection--;
}
}
@Override
public boolean isMultiSelect() {
return propertySupport.getPropertyBool(PROP_MULTI_SELECT);
}
@Override
public void setMultiSelect(boolean b) {
propertySupport.setPropertyBool(PROP_MULTI_SELECT, b);
}
@Override
public boolean isMultiCheck() {
return propertySupport.getPropertyBool(PROP_MULTI_CHECK);
}
@Override
public void setMultiCheck(boolean b) {
propertySupport.setPropertyBool(PROP_MULTI_CHECK, b);
}
@Override
public void unloadNode(ITreeNode node) throws ProcessingException {
try {
setTreeChanging(true);
//
setNodeExpanded(node, false);
removeAllChildNodes(node);
node.setChildrenLoaded(false);
}
finally {
setTreeChanging(false);
}
}
@Override
public void doHyperlinkAction(ITreeNode node, URL url) throws ProcessingException {
if (!m_actionRunning) {
try {
m_actionRunning = true;
if (node != null) {
selectNode(node);
execHyperlinkAction(url, url.getPath(), url != null && url.getHost().equals("local"));
}
}
finally {
m_actionRunning = false;
}
}
}
@Override
public void exportTreeData(final AbstractTreeFieldData target) throws ProcessingException {
exportTreeNodeDataRec(getRootNode().getChildNodes(), target, null);
}
private void exportTreeNodeDataRec(ITreeNode[] nodes, AbstractTreeFieldData treeData, TreeNodeData parentNodeData) throws ProcessingException {
ArrayList<TreeNodeData> nodeDataList = new ArrayList<TreeNodeData>();
for (ITreeNode node : nodes) {
TreeNodeData nodeData = exportTreeNodeData(node, treeData);
if (nodeData != null) {
exportTreeNodeDataRec(node.getChildNodes(), treeData, nodeData);
nodeDataList.add(nodeData);
}
}
if (parentNodeData != null) {
parentNodeData.setChildNodes(nodeDataList);
}
else {
treeData.setRoots(nodeDataList);
}
}
/**
* @return a node data for this tree node or null to skip this node
*/
protected TreeNodeData exportTreeNodeData(ITreeNode node, AbstractTreeFieldData treeData) throws ProcessingException {
TreeNodeData nodeData = new TreeNodeData();
return nodeData;
}
@Override
public void importTreeData(AbstractTreeFieldData source) throws ProcessingException {
if (source.isValueSet()) {
try {
setTreeChanging(true);
//
removeAllChildNodes(getRootNode());
importTreeNodeDataRec(getRootNode(), source, source.getRoots());
}
finally {
setTreeChanging(false);
}
}
}
private void importTreeNodeDataRec(ITreeNode parentNode, AbstractTreeFieldData treeData, List<TreeNodeData> nodeDataList) throws ProcessingException {
if (nodeDataList != null) {
for (TreeNodeData nodeData : nodeDataList) {
ITreeNode node = importTreeNodeData(parentNode, treeData, nodeData);
if (node != null) {
importTreeNodeDataRec(node, treeData, nodeData.getChildNodes());
}
}
}
}
/**
* @return the new tree node for this node data or null to skip this node
* It is the responsibility of this method to add the new nopde to the tree.
*/
protected ITreeNode importTreeNodeData(ITreeNode parentNode, AbstractTreeFieldData treeData, TreeNodeData nodeData) throws ProcessingException {
return null;
}
@Override
public ITreeUIFacade getUIFacade() {
return m_uiFacade;
}
@Override
public boolean isSaveAndRestoreScrollbars() {
return m_saveAndRestoreScrollbars;
}
@Override
public void setSaveAndRestoreScrollbars(boolean b) {
m_saveAndRestoreScrollbars = b;
}
private abstract class P_AbstractCollectingTreeVisitor implements ITreeVisitor {
private final ArrayList<ITreeNode> m_list = new ArrayList<ITreeNode>();
protected void addNodeToList(ITreeNode node) {
m_list.add(node);
}
public ITreeNode[] getNodes() {
return m_list.toArray(new ITreeNode[0]);
}
}// end private class
private abstract class P_AbstractCountingTreeVisitor implements ITreeVisitor {
private int m_count;
protected void addCount(int n) {
m_count += n;
}
public int getCount() {
return m_count;
}
}// end private class
/*
* UI Notifications
*/
private class P_UIFacade implements ITreeUIFacade {
private int m_uiProcessorCount = 0;
protected void pushUIProcessor() {
m_uiProcessorCount++;
}
protected void popUIProcessor() {
m_uiProcessorCount--;
}
@Override
public boolean isUIProcessing() {
return m_uiProcessorCount > 0;
}
@Override
public void setNodeExpandedFromUI(ITreeNode node, boolean on) {
try {
pushUIProcessor();
try {
setTreeChanging(true);
//
node = resolveNode(node);
node = resolveVirtualNode(node);
if (node != null) {
if (node.isExpanded() != on) {
if (on) {
if (node.isChildrenDirty() || node.isChildrenVolatile()) {
node.loadChildren();
}
}
setNodeExpanded(node, on);
}
}
}
finally {
setTreeChanging(false);
}
}
catch (ProcessingException se) {
se.addContextMessage(node.getCell().getText());
SERVICES.getService(IExceptionHandlerService.class).handleException(se);
}
finally {
popUIProcessor();
}
}
@Override
public void setNodeSelectedAndExpandedFromUI(ITreeNode node) {
try {
pushUIProcessor();
//
try {
setTreeChanging(true);
node = resolveNode(node);
node = resolveVirtualNode(node);
if (node != null) {
if (node.isChildrenDirty() || node.isChildrenVolatile()) {
node.loadChildren();
}
setNodeExpanded(node, true);
selectNode(node, false);
if (!isScrollToSelection()) {
scrollToSelection();
}
}
}
finally {
setTreeChanging(false);
}
}
catch (ProcessingException se) {
se.addContextMessage(node.getCell().getText());
SERVICES.getService(IExceptionHandlerService.class).handleException(se);
}
finally {
popUIProcessor();
}
}
@Override
public void setNodesSelectedFromUI(ITreeNode[] nodes) {
try {
pushUIProcessor();
try {
setTreeChanging(true);
Set<ITreeNode> validNodes = new HashSet<ITreeNode>(nodes.length);
for (ITreeNode n : resolveVirtualNodes(resolveNodes(nodes))) {
if (n.isFilterAccepted()) {
validNodes.add(n);
}
}
// load children for selection
for (ITreeNode node : validNodes) {
if (node.isChildrenLoaded()) {
if (node.isChildrenDirty() || node.isChildrenVolatile()) {
node.loadChildren();
}
}
}
selectNodes(validNodes.toArray(new ITreeNode[validNodes.size()]), false);
}
finally {
setTreeChanging(false);
}
}
catch (ProcessingException se) {
se.addContextMessage(Arrays.asList(nodes).toString());
SERVICES.getService(IExceptionHandlerService.class).handleException(se);
}
finally {
popUIProcessor();
}
}
@Override
public IMenu[] fireNodePopupFromUI() {
try {
pushUIProcessor();
//
ITreeNode[] nodes = resolveVirtualNodes(getSelectedNodes());
return fetchMenusForNodesInternal(nodes);
}
catch (ProcessingException e) {
SERVICES.getService(IExceptionHandlerService.class).handleException(e);
return new IMenu[0];
}
finally {
popUIProcessor();
}
}
@Override
public IMenu[] fireEmptySpacePopupFromUI() {
try {
pushUIProcessor();
//
return fetchMenusForNodesInternal(new ITreeNode[0]);
}
finally {
popUIProcessor();
}
}
@Override
public void fireNodeClickFromUI(ITreeNode node) {
try {
pushUIProcessor();
//
node = resolveNode(node);
node = resolveVirtualNode(node);
if (node != null) {
fireNodeClick(node);
}
}
catch (ProcessingException e) {
SERVICES.getService(IExceptionHandlerService.class).handleException(e);
}
finally {
popUIProcessor();
}
}
@Override
public void fireNodeActionFromUI(ITreeNode node) {
try {
pushUIProcessor();
//
node = resolveNode(node);
node = resolveVirtualNode(node);
if (node != null) {
fireNodeAction(node);
}
}
catch (ProcessingException e) {
SERVICES.getService(IExceptionHandlerService.class).handleException(e);
}
finally {
popUIProcessor();
}
}
@Override
public boolean getNodesDragEnabledFromUI() {
return isDragEnabled();
}
@Override
public TransferObject fireNodesDragRequestFromUI() {
try {
pushUIProcessor();
//
ITreeNode[] nodes = resolveVirtualNodes(getSelectedNodes());
return fireNodesDragRequest(nodes);
}
catch (ProcessingException e) {
SERVICES.getService(IExceptionHandlerService.class).handleException(e);
return null;
}
finally {
popUIProcessor();
}
}
@Override
public void fireNodeDropActionFromUI(ITreeNode node, TransferObject dropData) {
try {
pushUIProcessor();
//
node = resolveNode(node);
node = resolveVirtualNode(node);
if (node != null) {
fireNodeDropAction(node, dropData);
}
}
catch (ProcessingException e) {
SERVICES.getService(IExceptionHandlerService.class).handleException(e);
}
finally {
popUIProcessor();
}
}
@Override
public void fireHyperlinkActionFromUI(ITreeNode node, URL url) {
try {
pushUIProcessor();
//
node = resolveNode(node);
node = resolveVirtualNode(node);
if (node != null) {
doHyperlinkAction(node, url);
}
}
catch (ProcessingException e) {
SERVICES.getService(IExceptionHandlerService.class).handleException(e);
}
finally {
popUIProcessor();
}
}
}// end private class
}