| /* |
| * Copyright (c) 2010-2020 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.security.Permission; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.function.Function; |
| |
| import org.eclipse.scout.rt.client.AbstractClientSession; |
| import org.eclipse.scout.rt.client.ModelContextProxy; |
| import org.eclipse.scout.rt.client.ModelContextProxy.ModelContext; |
| import org.eclipse.scout.rt.client.extension.ui.action.tree.MoveActionNodesHandler; |
| import org.eclipse.scout.rt.client.extension.ui.basic.tree.ITreeExtension; |
| import org.eclipse.scout.rt.client.extension.ui.basic.tree.TreeChains.TreeAutoCheckChildNodesChain; |
| import org.eclipse.scout.rt.client.extension.ui.basic.tree.TreeChains.TreeDecorateCellChain; |
| import org.eclipse.scout.rt.client.extension.ui.basic.tree.TreeChains.TreeDisposeTreeChain; |
| import org.eclipse.scout.rt.client.extension.ui.basic.tree.TreeChains.TreeDragNodeChain; |
| import org.eclipse.scout.rt.client.extension.ui.basic.tree.TreeChains.TreeDragNodesChain; |
| import org.eclipse.scout.rt.client.extension.ui.basic.tree.TreeChains.TreeDropChain; |
| import org.eclipse.scout.rt.client.extension.ui.basic.tree.TreeChains.TreeDropTargetChangedChain; |
| import org.eclipse.scout.rt.client.extension.ui.basic.tree.TreeChains.TreeHyperlinkActionChain; |
| import org.eclipse.scout.rt.client.extension.ui.basic.tree.TreeChains.TreeInitTreeChain; |
| import org.eclipse.scout.rt.client.extension.ui.basic.tree.TreeChains.TreeNodeActionChain; |
| import org.eclipse.scout.rt.client.extension.ui.basic.tree.TreeChains.TreeNodeClickChain; |
| import org.eclipse.scout.rt.client.extension.ui.basic.tree.TreeChains.TreeNodesCheckedChain; |
| import org.eclipse.scout.rt.client.extension.ui.basic.tree.TreeChains.TreeNodesSelectedChain; |
| import org.eclipse.scout.rt.client.services.common.icon.IIconProviderService; |
| import org.eclipse.scout.rt.client.ui.AbstractEventBuffer; |
| import org.eclipse.scout.rt.client.ui.AbstractWidget; |
| import org.eclipse.scout.rt.client.ui.IEventHistory; |
| import org.eclipse.scout.rt.client.ui.IWidget; |
| import org.eclipse.scout.rt.client.ui.MouseButton; |
| 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.MenuUtility; |
| import org.eclipse.scout.rt.client.ui.action.menu.root.ITreeContextMenu; |
| import org.eclipse.scout.rt.client.ui.action.menu.root.internal.TreeContextMenu; |
| import org.eclipse.scout.rt.client.ui.basic.cell.Cell; |
| import org.eclipse.scout.rt.client.ui.basic.userfilter.IUserFilter; |
| import org.eclipse.scout.rt.client.ui.dnd.IDNDSupport; |
| import org.eclipse.scout.rt.client.ui.dnd.TransferObject; |
| import org.eclipse.scout.rt.platform.BEANS; |
| import org.eclipse.scout.rt.platform.Order; |
| import org.eclipse.scout.rt.platform.annotations.ConfigOperation; |
| import org.eclipse.scout.rt.platform.annotations.ConfigProperty; |
| import org.eclipse.scout.rt.platform.classid.ClassId; |
| import org.eclipse.scout.rt.platform.exception.ExceptionHandler; |
| import org.eclipse.scout.rt.platform.exception.PlatformExceptionTranslator; |
| import org.eclipse.scout.rt.platform.holders.Holder; |
| import org.eclipse.scout.rt.platform.reflect.ConfigurationUtility; |
| import org.eclipse.scout.rt.platform.util.CollectionUtility; |
| import org.eclipse.scout.rt.platform.util.collection.OrderedCollection; |
| import org.eclipse.scout.rt.platform.util.visitor.CollectingVisitor; |
| import org.eclipse.scout.rt.platform.util.visitor.DepthFirstTreeVisitor; |
| import org.eclipse.scout.rt.platform.util.visitor.IDepthFirstTreeVisitor; |
| import org.eclipse.scout.rt.platform.util.visitor.TreeTraversals; |
| import org.eclipse.scout.rt.platform.util.visitor.TreeVisitResult; |
| import org.eclipse.scout.rt.shared.data.basic.NamedBitMaskHelper; |
| 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.dimension.IDimensions; |
| import org.eclipse.scout.rt.shared.extension.AbstractExtension; |
| import org.eclipse.scout.rt.shared.extension.ContributionComposite; |
| import org.eclipse.scout.rt.shared.extension.IContributionOwner; |
| import org.eclipse.scout.rt.shared.extension.IExtensibleObject; |
| import org.eclipse.scout.rt.shared.extension.IExtension; |
| import org.eclipse.scout.rt.shared.extension.ObjectExtensions; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| @ClassId("b177affd-790b-4908-b608-ac00b996b10e") |
| public abstract class AbstractTree extends AbstractWidget implements ITree, IContributionOwner, IExtensibleObject { |
| |
| private static final String AUTO_DISCARD_ON_DELETE = "AUTO_DISCARD_ON_DELETE"; |
| private static final String AUTO_TITLE = "AUTO_TITLE"; |
| private static final String ACTION_RUNNING = "ACTION_RUNNING"; |
| private static final String SAVE_AND_RESTORE_SCROLLBARS = "SAVE_AND_RESTORE_SCROLLBARS"; |
| private static final Logger LOG = LoggerFactory.getLogger(AbstractTree.class); |
| private static final NamedBitMaskHelper FLAGS_BIT_HELPER = new NamedBitMaskHelper(AUTO_DISCARD_ON_DELETE, AUTO_TITLE, ACTION_RUNNING, SAVE_AND_RESTORE_SCROLLBARS); |
| |
| private final TreeListeners m_listeners = new TreeListeners(); |
| |
| private final Set<ITreeNode> m_checkedNodes; |
| private final Map<Object, ITreeNode> m_deletedNodes; |
| private final List<ITreeNodeFilter> m_nodeFilters; |
| private final ObjectExtensions<AbstractTree, ITreeExtension<? extends AbstractTree>> m_objectExtensions; |
| |
| /** |
| * In autoCheckChildren mode only the effectively checked parent nodes should cause nodesChecked Events (to minimize |
| * network traffic). setNodesChecked is called recursively by the intercepter. The m_currentParentNodes list is used |
| * to avoid firing events inside the recursion. |
| */ |
| private List<ITreeNode> m_currentParentNodes; |
| |
| /** |
| * Provides 4 boolean flags.<br> |
| * Currently used: {@link #AUTO_DISCARD_ON_DELETE}, {@link #AUTO_TITLE}, |
| * {@link #ACTION_RUNNING}, {@link #SAVE_AND_RESTORE_SCROLLBARS} |
| */ |
| private byte m_flags; |
| |
| private ITreeNode m_rootNode; |
| private int m_treeChanging; |
| private AbstractEventBuffer<TreeEvent> m_eventBuffer; |
| |
| private ITreeUIFacade m_uiFacade; |
| private Set<ITreeNode> m_nodeDecorationBuffer = new HashSet<>(); |
| private Set<ITreeNode> m_selectedNodes = new HashSet<>(); |
| private List<IKeyStroke> m_baseKeyStrokes; |
| private IEventHistory<TreeEvent> m_eventHistory; |
| private ITreeNode m_lastSeenDropNode; |
| private IContributionOwner m_contributionHolder; |
| private List<IMenu> m_currentNodeMenus; |
| |
| public AbstractTree() { |
| this(true); |
| } |
| |
| public AbstractTree(boolean callInitializer) { |
| super(false); |
| m_checkedNodes = new HashSet<>(); |
| m_deletedNodes = new HashMap<>(); |
| m_nodeFilters = new ArrayList<>(1); |
| m_objectExtensions = new ObjectExtensions<>(this, false); |
| if (callInitializer) { |
| callInitializer(); |
| } |
| } |
| |
| @Override |
| protected void initConfigInternal() { |
| m_objectExtensions.initConfig(createLocalExtension(), this::initConfig); |
| } |
| |
| @Override |
| public final List<Object> getAllContributions() { |
| return m_contributionHolder.getAllContributions(); |
| } |
| |
| @Override |
| public final <T> List<T> getContributionsByClass(Class<T> type) { |
| return m_contributionHolder.getContributionsByClass(type); |
| } |
| |
| @Override |
| public final <T> T getContribution(Class<T> contribution) { |
| return m_contributionHolder.getContribution(contribution); |
| } |
| |
| @Override |
| public final <T> T optContribution(Class<T> contribution) { |
| return m_contributionHolder.optContribution(contribution); |
| } |
| |
| /* |
| * Configuration |
| */ |
| /** |
| * Configures the title of the tree. |
| */ |
| @ConfigProperty(ConfigProperty.TEXT) |
| @Order(10) |
| protected String getConfiguredTitle() { |
| return null; |
| } |
| |
| /** |
| * Configures the icon of the tree. |
| * |
| * @return the ID (name) of the icon |
| * @see IIconProviderService |
| */ |
| @ConfigProperty(ConfigProperty.ICON_ID) |
| @Order(20) |
| protected String getConfiguredIconId() { |
| return null; |
| } |
| |
| /** |
| * Configures the default iconId to be used for all tree nodes without an own icon. |
| * <p> |
| * Subclasses can override this method. Default is {@code null}. |
| * |
| * @return the ID (name) of the icon |
| * @see IIconProviderService |
| */ |
| @ConfigProperty(ConfigProperty.ICON_ID) |
| @Order(21) |
| protected String getConfiguredDefaultIconId() { |
| return null; |
| } |
| |
| @ConfigProperty(ConfigProperty.BOOLEAN) |
| @Order(30) |
| protected boolean getConfiguredAutoTitle() { |
| return false; |
| } |
| |
| /** |
| * Multi-select is not supported by the HTML UI yet. Therefore the configured method is final for the moment. |
| */ |
| @Order(40) |
| @ConfigProperty(ConfigProperty.BOOLEAN) |
| protected final 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; |
| } |
| |
| /** |
| * <p> |
| * Configures the drag support of this tree. |
| * </p> |
| * <p> |
| * Method marked as final as currently only drop is implemented for this field. |
| * </p> |
| * |
| * @return {@code 0} for no support or one or more of {@link IDNDSupport#TYPE_FILE_TRANSFER}, |
| * {@link IDNDSupport#TYPE_IMAGE_TRANSFER}, {@link IDNDSupport#TYPE_JAVA_ELEMENT_TRANSFER} or |
| * {@link IDNDSupport#TYPE_TEXT_TRANSFER} (e.g. {@code TYPE_TEXT_TRANSFER | TYPE_FILE_TRANSFER}). |
| */ |
| @ConfigProperty(ConfigProperty.DRAG_AND_DROP_TYPE) |
| @Order(51) |
| protected final int getConfiguredDragType() { |
| return 0; |
| } |
| |
| /** |
| * Configures the drop support of this tree. |
| * <p> |
| * Subclasses can override this method. Default is {@code 0} (no drop support). |
| * |
| * @return {@code 0} for no support or one or more of {@link IDNDSupport#TYPE_FILE_TRANSFER}, |
| * {@link IDNDSupport#TYPE_IMAGE_TRANSFER}, {@link IDNDSupport#TYPE_JAVA_ELEMENT_TRANSFER} or |
| * {@link IDNDSupport#TYPE_TEXT_TRANSFER} (e.g. {@code TYPE_TEXT_TRANSFER | TYPE_FILE_TRANSFER}). |
| */ |
| @ConfigProperty(ConfigProperty.DRAG_AND_DROP_TYPE) |
| @Order(52) |
| protected int getConfiguredDropType() { |
| return 0; |
| } |
| |
| /** |
| * Configures the maximum size for a drop request (in bytes). |
| * <p> |
| * Subclasses can override this method. Default is defined by {@link IDNDSupport#DEFAULT_DROP_MAXIMUM_SIZE}. |
| * |
| * @return maximum size in bytes. |
| */ |
| @ConfigProperty(ConfigProperty.LONG) |
| @Order(190) |
| protected long getConfiguredDropMaximumSize() { |
| return DEFAULT_DROP_MAXIMUM_SIZE; |
| } |
| |
| /** |
| * @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 AbstractClientSession} upon |
| * detaching the UI component from Scout. The coordinates are restored (if the coordinates 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; |
| } |
| |
| /** |
| * Checks / unchecks all visible child nodes if the parent node gets checked / unchecked. |
| * <p> |
| * Only has an effect if the tree is checkable. |
| * |
| * @see #getConfiguredCheckable() |
| * @since 5.1 |
| */ |
| @ConfigProperty(ConfigProperty.BOOLEAN) |
| @Order(100) |
| protected boolean getConfiguredAutoCheckChildNodes() { |
| return false; |
| } |
| |
| /** |
| * Configures whether it should be possible that child nodes may added lazily to the tree when expanding the node. |
| * This property controls whether the feature is available at all. If set to true you need to define which nodes are |
| * affected by using {@link ITreeNode#setLazyExpandingEnabled(boolean)} |
| * <p> |
| * Subclasses can override this method. Default is {@code true}. |
| * |
| * @see ITreeNode#isLazyExpandingEnabled() |
| * @see ITreeNode#isExpandedLazy() |
| */ |
| @ConfigProperty(ConfigProperty.BOOLEAN) |
| @Order(110) |
| protected boolean getConfiguredLazyExpandingEnabled() { |
| return true; |
| } |
| |
| /** |
| * Configures the display style of the tree. |
| * <p> |
| * The available styles are: |
| * <ul> |
| * <li>{@link ITree#DISPLAY_STYLE_DEFAULT}</li> |
| * <li>{@link ITree#DISPLAY_STYLE_BREADCRUMB}</li> |
| * </ul> |
| * <p> |
| * Subclasses can override this method. The default is {@link ITree#DISPLAY_STYLE_DEFAULT}. |
| * |
| * @see #getConfiguredToggleBreadcrumbStyleEnabled() |
| */ |
| @ConfigProperty(ConfigProperty.BOOLEAN) |
| @Order(140) |
| protected String getConfiguredDisplayStyle() { |
| return DISPLAY_STYLE_DEFAULT; |
| } |
| |
| /** |
| * Configures whether the tree should automatically switch to the bread crumb style when getting smaller and back when |
| * getting bigger. The threshold is determined by the GUI. |
| * <p> |
| * Subclasses can override this method. The default is false. |
| * |
| * @see #getConfiguredDisplayStyle() |
| */ |
| @ConfigProperty(ConfigProperty.BOOLEAN) |
| @Order(145) |
| protected boolean getConfiguredToggleBreadcrumbStyleEnabled() { |
| return false; |
| } |
| |
| @ConfigProperty(ConfigProperty.OBJECT) |
| @Order(155) |
| protected CheckableStyle getConfiguredCheckableStyle() { |
| return CheckableStyle.CHECKBOX_TREE_NODE; |
| } |
| |
| private List<Class<? extends IKeyStroke>> getConfiguredKeyStrokes() { |
| Class<?>[] dca = ConfigurationUtility.getDeclaredPublicClasses(getClass()); |
| List<Class<IKeyStroke>> fca = ConfigurationUtility.filterClasses(dca, IKeyStroke.class); |
| return ConfigurationUtility.removeReplacedClasses(fca); |
| } |
| |
| protected List<Class<? extends IMenu>> getDeclaredMenus() { |
| Class<?>[] dca = ConfigurationUtility.getDeclaredPublicClasses(getClass()); |
| List<Class<IMenu>> filtered = ConfigurationUtility.filterClasses(dca, IMenu.class); |
| return ConfigurationUtility.removeReplacedClasses(filtered); |
| } |
| |
| @Override |
| public boolean isAutoCheckChildNodes() { |
| return propertySupport.getPropertyBool(PROP_AUTO_CHECK_CHILDREN); |
| } |
| |
| @Override |
| public void setAutoCheckChildNodes(boolean b) { |
| propertySupport.setPropertyBool(PROP_AUTO_CHECK_CHILDREN, b); |
| } |
| |
| @ConfigOperation |
| @Order(10) |
| protected void execInitTree() { |
| } |
| |
| @ConfigOperation |
| @Order(15) |
| protected void execDisposeTree() { |
| } |
| |
| /** |
| * Called when an app link has been clicked. |
| * <p> |
| * Subclasses can override this method. The default does nothing. |
| */ |
| @ConfigOperation |
| @Order(18) |
| protected void execAppLinkAction(String ref) { |
| } |
| |
| /** |
| * this method should not be implemented if you support {@link #interceptDrag(Collection)} (drag of |
| * multiple nodes), as it takes precedence |
| * |
| * @return a transferable object representing the given row |
| */ |
| @ConfigOperation |
| @Order(20) |
| protected final TransferObject execDrag(ITreeNode node) { |
| return null; |
| } |
| |
| /** |
| * Drag of multiple nodes. If this method is implemented, also single drags will be handled by Scout, the method |
| * {@link AbstractTree#interceptDrag(ITreeNode)} must not be implemented then. |
| * |
| * @return a transferable object representing the given rows |
| */ |
| @ConfigOperation |
| @Order(30) |
| protected TransferObject execDrag(Collection<ITreeNode> nodes) { |
| return null; |
| } |
| |
| /** |
| * process drop action |
| */ |
| @ConfigOperation |
| @Order(40) |
| protected void execDrop(ITreeNode node, TransferObject t) { |
| } |
| |
| /** |
| * This method gets called when the drop node is changed, e.g. the dragged object is moved over a new drop target. |
| * |
| * @since 4.0-M7 |
| */ |
| @ConfigOperation |
| @Order(45) |
| protected void execDropTargetChanged(ITreeNode node) { |
| } |
| |
| /** |
| * decoration for every cell calls this method |
| * <p> |
| * Default delegates to {@link ITreeNode#decorateCell()} |
| */ |
| @ConfigOperation |
| @Order(50) |
| protected void execDecorateCell(ITreeNode node, Cell cell) { |
| if (cell.getIconId() == null && getDefaultIconId() != null) { |
| cell.setIconId(getDefaultIconId()); |
| } |
| node.decorateCell(); |
| } |
| |
| @ConfigOperation |
| @Order(60) |
| protected void execNodesSelected(TreeEvent e) { |
| } |
| |
| @ConfigOperation |
| @Order(70) |
| protected void execNodeClick(ITreeNode node, MouseButton mouseButton) { |
| TreeEvent e = new TreeEvent(this, TreeEvent.TYPE_NODE_CLICK, node); |
| fireTreeEventInternal(e); |
| } |
| |
| @ConfigOperation |
| @Order(80) |
| protected void execNodeAction(ITreeNode node) { |
| TreeEvent e = new TreeEvent(this, TreeEvent.TYPE_NODE_ACTION, node); |
| fireTreeEventInternal(e); |
| } |
| |
| /** |
| * Called when nodes get checked or unchecked. |
| * <p> |
| * Subclasses can override this method. |
| * |
| * @param nodes |
| * list of nodes which have been checked or unchecked (never null). |
| */ |
| @ConfigOperation |
| @Order(90) |
| protected void execNodesChecked(List<ITreeNode> nodes) { |
| } |
| |
| @ConfigOperation |
| protected void execAutoCheckChildNodes(List<? extends ITreeNode> nodes, boolean checked, boolean enabledNodesOnly) { |
| for (ITreeNode node : nodes) { |
| for (ITreeNode childNode : node.getFilteredChildNodes()) { |
| childNode.setChecked(checked, enabledNodesOnly); |
| interceptAutoCheckChildNodes(CollectionUtility.arrayList(childNode), checked, enabledNodesOnly); |
| } |
| } |
| } |
| |
| @Override |
| protected void initConfig() { |
| super.initConfig(); |
| m_eventHistory = createEventHistory(); |
| m_eventBuffer = createEventBuffer(); |
| m_uiFacade = BEANS.get(ModelContextProxy.class).newProxy(createUIFacade(), ModelContext.copyCurrent()); |
| m_contributionHolder = new ContributionComposite(this); |
| setTitle(getConfiguredTitle()); |
| setIconId(getConfiguredIconId()); |
| setDefaultIconId(getConfiguredDefaultIconId()); |
| setCssClass(getConfiguredCssClass()); |
| setAutoTitle(getConfiguredAutoTitle()); |
| setCheckable(getConfiguredCheckable()); |
| setCheckableStyle(getConfiguredCheckableStyle()); |
| setNodeHeightHint(getConfiguredNodeHeightHint()); |
| setMultiCheck(getConfiguredMultiCheck()); |
| setMultiSelect(getConfiguredMultiSelect()); |
| setAutoDiscardOnDelete(getConfiguredAutoDiscardOnDelete()); |
| setDragEnabled(getConfiguredDragEnabled()); |
| setDragType(getConfiguredDragType()); |
| setDropType(getConfiguredDropType()); |
| setDropMaximumSize(getConfiguredDropMaximumSize()); |
| setRootNodeVisible(getConfiguredRootNodeVisible()); |
| setRootHandlesVisible(getConfiguredRootHandlesVisible()); |
| setScrollToSelection(getConfiguredScrollToSelection()); |
| setSaveAndRestoreScrollbars(getConfiguredSaveAndRestoreScrollbars()); |
| setAutoCheckChildNodes(getConfiguredAutoCheckChildNodes()); |
| setLazyExpandingEnabled(getConfiguredLazyExpandingEnabled()); |
| setDisplayStyle(getConfiguredDisplayStyle()); |
| setToggleBreadcrumbStyleEnabled(getConfiguredToggleBreadcrumbStyleEnabled()); |
| setRootNode(new AbstractTreeNode() { |
| }); |
| // add Convenience observer for drag & drop callbacks and event history |
| addTreeListener( |
| e -> { |
| //event history |
| IEventHistory<TreeEvent> h = getEventHistory(); |
| if (h != null) { |
| h.notifyEvent(e); |
| } |
| //dnd |
| switch (e.getType()) { |
| case TreeEvent.TYPE_NODES_DRAG_REQUEST: { |
| m_lastSeenDropNode = null; |
| if (e.getDragObject() == null) { |
| try { |
| TransferObject transferObject = interceptDrag(e.getNode()); |
| if (transferObject == null) { |
| transferObject = interceptDrag(e.getNodes()); |
| } |
| e.setDragObject(transferObject); |
| } |
| catch (Exception t) { |
| LOG.error("Drag", t); |
| } |
| } |
| break; |
| } |
| case TreeEvent.TYPE_NODE_DROP_ACTION: { |
| m_lastSeenDropNode = null; |
| if (e.getDropObject() != null) { |
| try { |
| interceptDrop(e.getNode(), e.getDropObject()); |
| } |
| catch (Exception t) { |
| LOG.error("Drop", t); |
| } |
| } |
| break; |
| } |
| case TreeEvent.TYPE_NODES_SELECTED: { |
| rebuildKeyStrokesInternal(); |
| break; |
| } |
| case TreeEvent.TYPE_NODES_CHECKED: { |
| try { |
| interceptNodesChecked(CollectionUtility.arrayList(e.getNodes())); |
| } |
| catch (RuntimeException ex) { |
| BEANS.get(ExceptionHandler.class).handle(ex); |
| } |
| break; |
| } |
| case TreeEvent.TYPE_NODE_DROP_TARGET_CHANGED: { |
| try { |
| if (m_lastSeenDropNode == null || m_lastSeenDropNode != e.getNode()) { |
| m_lastSeenDropNode = e.getNode(); |
| interceptDropTargetChanged(e.getNode()); |
| } |
| } |
| catch (RuntimeException ex) { |
| LOG.error("DropTargetChanged", ex); |
| } |
| break; |
| } |
| case TreeEvent.TYPE_DRAG_FINISHED: { |
| m_lastSeenDropNode = null; |
| } |
| } |
| }, |
| TreeEvent.TYPE_NODES_DRAG_REQUEST, |
| TreeEvent.TYPE_NODE_DROP_ACTION, |
| TreeEvent.TYPE_NODES_SELECTED, |
| TreeEvent.TYPE_NODES_CHECKED, |
| TreeEvent.TYPE_NODE_DROP_TARGET_CHANGED, |
| TreeEvent.TYPE_DRAG_FINISHED); |
| // key shortcuts |
| List<Class<? extends IKeyStroke>> configuredKeyStrokes = getConfiguredKeyStrokes(); |
| List<IKeyStroke> ksList = new ArrayList<>(configuredKeyStrokes.size()); |
| for (Class<? extends IKeyStroke> keystrokeClazz : configuredKeyStrokes) { |
| ksList.add(ConfigurationUtility.newInnerInstance(this, keystrokeClazz)); |
| } |
| //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() { |
| fireNodeAction(getSelectedNode()); |
| } |
| }); |
| } |
| |
| List<IKeyStroke> contributedKeyStrokes = m_contributionHolder.getContributionsByClass(IKeyStroke.class); |
| ksList.addAll(contributedKeyStrokes); |
| |
| m_baseKeyStrokes = ksList; |
| setKeyStrokesInternal(m_baseKeyStrokes); |
| |
| // menus |
| List<Class<? extends IMenu>> declaredMenus = getDeclaredMenus(); |
| List<IMenu> contributedMenus = m_contributionHolder.getContributionsByClass(IMenu.class); |
| |
| OrderedCollection<IMenu> menus = new OrderedCollection<>(); |
| for (Class<? extends IMenu> menuClazz : declaredMenus) { |
| IMenu menu = ConfigurationUtility.newInnerInstance(this, menuClazz); |
| menus.addOrdered(menu); |
| } |
| try { |
| injectMenusInternal(menus); |
| } |
| catch (Exception e) { |
| LOG.error("Error occurred while dynamically contributing menus.", e); |
| } |
| |
| menus.addAllOrdered(contributedMenus); |
| new MoveActionNodesHandler<>(menus).moveModelObjects(); |
| TreeContextMenu contextMenu = new TreeContextMenu(this, menus.getOrderedList()); |
| setContextMenuInternal(contextMenu); |
| } |
| |
| /* |
| * Runtime |
| */ |
| @Override |
| protected void initInternal() { |
| super.initInternal(); |
| initTreeInternal(); |
| interceptInitTree(); |
| } |
| |
| @Override |
| public List<? extends IWidget> getChildren() { |
| return CollectionUtility.flatten(super.getChildren(), getMenus(), getKeyStrokesInternal()); |
| } |
| |
| protected void initTreeInternal() { |
| } |
| |
| @Override |
| protected final void disposeInternal() { |
| disposeTreeInternal(); |
| try { |
| interceptDisposeTree(); |
| } |
| catch (Exception e) { |
| LOG.warn("Exception while disposing tree", e); |
| } |
| super.disposeInternal(); |
| } |
| |
| protected void disposeTreeInternal() { |
| getRootNode().dispose(); |
| clearDeletedNodes(); |
| } |
| |
| @Override |
| public final List<? extends ITreeExtension<? extends AbstractTree>> getAllExtensions() { |
| return m_objectExtensions.getAllExtensions(); |
| } |
| |
| protected ITreeExtension<? extends AbstractTree> createLocalExtension() { |
| return new LocalTreeExtension<>(this); |
| } |
| |
| @Override |
| public <T extends IExtension<?>> T getExtension(Class<T> c) { |
| return m_objectExtensions.getExtension(c); |
| } |
| |
| /** |
| * Override this internal method only in order to make use of dynamic menus<br/> |
| * Used to manage menu list and add/remove menus.<br> |
| * To change the order or specify the insert position use {@link IMenu#setOrder(double)}. |
| * |
| * @param menus |
| * live and mutable collection of configured menus |
| */ |
| protected void injectMenusInternal(OrderedCollection<IMenu> menus) { |
| } |
| |
| @Override |
| public ITreeContextMenu getContextMenu() { |
| return (ITreeContextMenu) propertySupport.getProperty(PROP_CONTEXT_MENU); |
| } |
| |
| protected void setContextMenuInternal(ITreeContextMenu contextMenu) { |
| propertySupport.setProperty(PROP_CONTEXT_MENU, contextMenu); |
| } |
| |
| @Override |
| public List<IMenu> getMenus() { |
| return getContextMenu().getChildActions(); |
| } |
| |
| @Override |
| public <T extends IMenu> T getMenuByClass(Class<T> menuType) { |
| return MenuUtility.getMenuByClass(this, menuType); |
| } |
| |
| @Override |
| public boolean hasNodeFilters() { |
| return !m_nodeFilters.isEmpty(); |
| } |
| |
| @Override |
| public List<ITreeNodeFilter> getNodeFilters() { |
| return CollectionUtility.arrayList(m_nodeFilters); |
| } |
| |
| @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; |
| } |
| List<ITreeNodeFilter> rejectingFilters = new ArrayList<>(); |
| inode.setFilterAccepted(true); |
| inode.setRejectedByUser(false); |
| if (!m_nodeFilters.isEmpty()) { |
| for (ITreeNodeFilter filter : m_nodeFilters) { |
| if (!filter.accept(inode, level)) { |
| inode.setFilterAccepted(false); |
| rejectingFilters.add(filter); |
| } |
| } |
| } |
| |
| // Prefer inode.isRejectedByUser to allow a filter to set this flag |
| inode.setRejectedByUser(inode.isRejectedByUser() |
| || (rejectingFilters.size() == 1 && rejectingFilters.get(0) instanceof IUserFilter)); |
| |
| 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) { |
| tmp.setFilterAccepted(true); |
| tmp.setRejectedByUser(false); |
| tmp = tmp.getParentNode(); |
| } |
| } |
| // children |
| for (ITreeNode child : inode.getChildNodes()) { |
| applyNodeFiltersRecInternal(child, inode.isFilterAccepted(), level + 1); |
| } |
| } |
| |
| @Override |
| public AbstractEventBuffer<TreeEvent> createEventBuffer() { |
| return BEANS.get(TreeEventBuffer.class); |
| } |
| |
| protected AbstractEventBuffer<TreeEvent> getEventBuffer() { |
| return m_eventBuffer; |
| } |
| |
| @Override |
| public void requestFocus() { |
| fireRequestFocus(); |
| } |
| |
| @Override |
| public ITreeNode getRootNode() { |
| return m_rootNode; |
| } |
| |
| @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 FLAGS_BIT_HELPER.isBitSet(AUTO_TITLE, m_flags); |
| } |
| |
| @Override |
| public void setAutoTitle(boolean b) { |
| m_flags = FLAGS_BIT_HELPER.changeBit(AUTO_TITLE, b, m_flags); |
| } |
| |
| @Override |
| public String getIconId() { |
| return propertySupport.getPropertyString(PROP_ICON_ID); |
| } |
| |
| @Override |
| public void setIconId(String iconId) { |
| propertySupport.setPropertyString(PROP_ICON_ID, iconId); |
| } |
| |
| @Override |
| public String getDefaultIconId() { |
| return propertySupport.getPropertyString(PROP_DEFAULT_ICON_ID); |
| } |
| |
| @Override |
| public void setDefaultIconId(String defaultIconId) { |
| propertySupport.setPropertyString(PROP_DEFAULT_ICON_ID, defaultIconId); |
| } |
| |
| @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 setDropMaximumSize(long dropMaximumSize) { |
| propertySupport.setPropertyLong(PROP_DROP_MAXIMUM_SIZE, dropMaximumSize); |
| } |
| |
| @Override |
| public long getDropMaximumSize() { |
| return propertySupport.getPropertyInt(PROP_DROP_MAXIMUM_SIZE); |
| } |
| |
| @Override |
| public boolean isLazyExpandingEnabled() { |
| return propertySupport.getPropertyBool(PROP_LAZY_EXPANDING_ENABLED); |
| } |
| |
| @Override |
| public void setLazyExpandingEnabled(boolean lazyExpandingEnabled) { |
| propertySupport.setPropertyBool(PROP_LAZY_EXPANDING_ENABLED, lazyExpandingEnabled); |
| } |
| |
| @Override |
| public String getDisplayStyle() { |
| return propertySupport.getPropertyString(PROP_DISPLAY_STYLE); |
| } |
| |
| @Override |
| public void setDisplayStyle(String style) { |
| propertySupport.setPropertyString(PROP_DISPLAY_STYLE, style); |
| } |
| |
| @Override |
| public boolean isToggleBreadcrumbStyleEnabled() { |
| return propertySupport.getPropertyBool(PROP_TOGGLE_BREADCRUMB_STYLE_ENABLED); |
| } |
| |
| @Override |
| public void setToggleBreadcrumbStyleEnabled(boolean b) { |
| propertySupport.setPropertyBool(PROP_TOGGLE_BREADCRUMB_STYLE_ENABLED, b); |
| } |
| |
| @Override |
| public CheckableStyle getCheckableStyle() { |
| return (CheckableStyle) propertySupport.getProperty(PROP_CHECKABLE_STYLE); |
| } |
| |
| @Override |
| public void setCheckableStyle(CheckableStyle checkableStyle) { |
| propertySupport.setProperty(PROP_CHECKABLE_STYLE, checkableStyle); |
| } |
| |
| @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(); |
| StringBuilder pathStr = new StringBuilder(); |
| ITreeNode node = selectedNode; |
| while (node != null) { |
| if (node != root || isRootNodeVisible()) { |
| if (pathStr.length() != 0) { |
| pathStr.insert(0, delimiter); |
| } |
| pathStr.insert(0, node.getCell().toPlainText()); |
| } |
| // next |
| node = node.getParentNode(); |
| } |
| return pathStr.toString(); |
| } |
| |
| private void rebuildTitleInternal() { |
| setTitle(getPathText(getSelectedNode())); |
| } |
| |
| private void rebuildKeyStrokesInternal() { |
| setKeyStrokesInternal(m_baseKeyStrokes); |
| } |
| |
| @Override |
| public ITreeNode findNode(Object primaryKey) { |
| Collection<ITreeNode> a = findNodes(CollectionUtility.hashSet(primaryKey)); |
| if (a != null && !a.isEmpty()) { |
| return CollectionUtility.firstElement(a); |
| } |
| else { |
| return null; |
| } |
| } |
| |
| @Override |
| public List<ITreeNode> findNodes(final Collection<?> primaryKeys) { |
| if (primaryKeys == null || primaryKeys.size() <= 0) { |
| return CollectionUtility.emptyArrayList(); |
| } |
| |
| final Set<Object> keySet = new HashSet<>(primaryKeys); |
| CollectingVisitor<ITreeNode> v = new CollectingVisitor<ITreeNode>() { |
| |
| @Override |
| public TreeVisitResult preVisit(ITreeNode element, int level, int index) { |
| super.preVisit(element, level, index); |
| if (keySet.isEmpty()) { |
| return TreeVisitResult.TERMINATE; |
| } |
| return TreeVisitResult.CONTINUE; |
| } |
| |
| @Override |
| protected boolean accept(ITreeNode node) { |
| return keySet.remove(node.getPrimaryKey()); |
| } |
| }; |
| visitNode(getRootNode(), v); |
| return v.getCollection(); |
| } |
| |
| @Override |
| public void setRootNode(ITreeNode root) { |
| if (m_rootNode != null) { |
| m_rootNode.setTreeInternal(null, true); |
| // inform root of remove |
| m_rootNode.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 (RuntimeException 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 { |
| processTreeBuffers(); |
| } |
| 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 expanded) { |
| boolean lazy; |
| if (node.isExpanded() == expanded) { |
| // no state change: Keep the current "expandedLazy" state |
| lazy = node.isExpandedLazy(); |
| } |
| else if (expanded) { |
| // collapsed -> expanded: Set the "expandedLazy" state to the node's "lazyExpandingEnabled" flag |
| lazy = node.isLazyExpandingEnabled(); |
| } |
| else { |
| // expanded -> collapsed: Set the "expandedLazy" state to false |
| lazy = false; |
| } |
| setNodeExpanded(node, expanded, lazy); |
| } |
| |
| @Override |
| public void setNodeExpanded(ITreeNode node, boolean expand, boolean lazy) { |
| // Never do lazy expansion if it is disabled on the tree |
| if (!isLazyExpandingEnabled()) { |
| lazy = false; |
| } |
| |
| node = resolveNode(node); |
| if (node != null && (node.isExpanded() != expand || node.isExpandedLazy() != lazy)) { |
| setNodeExpandedInternal(node, expand, lazy); |
| } |
| } |
| |
| @Override |
| public void setNodeExpandedInternal(ITreeNode node, boolean expand, boolean lazy) { |
| if (expand) { |
| node.ensureChildrenLoaded(); |
| ensureParentExpanded(node.getParentNode()); |
| } |
| node.setExpandedInternal(expand); |
| node.setExpandedLazyInternal(lazy); |
| fireNodeExpanded(node, expand); |
| } |
| |
| @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 FLAGS_BIT_HELPER.isBitSet(AUTO_DISCARD_ON_DELETE, m_flags); |
| } |
| |
| @Override |
| public void setAutoDiscardOnDelete(boolean on) { |
| m_flags = FLAGS_BIT_HELPER.changeBit(AUTO_DISCARD_ON_DELETE, on, m_flags); |
| } |
| |
| @Override |
| public void setNodeEnabledPermission(ITreeNode node, Permission p) { |
| node = resolveNode(node); |
| if (node == null) { |
| return; |
| } |
| boolean oldValue = node.isEnabled(); |
| AbstractTreeNode.setEnabledPermission(p, node); |
| boolean newValue = node.isEnabled(); |
| if (oldValue != newValue) { |
| fireNodesUpdated(node.getParentNode(), CollectionUtility.hashSet(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 enabled) { |
| node = resolveNode(node); |
| if (node != null) { |
| boolean oldValue = node.isEnabled(); |
| node.setEnabled(enabled, IDimensions.ENABLED); |
| boolean newValue = node.isEnabled(); |
| if (oldValue != newValue) { |
| fireNodesUpdated(node.getParentNode(), CollectionUtility.arrayList(node)); |
| } |
| } |
| } |
| |
| @Override |
| public void setNodeEnabledGranted(ITreeNode node, boolean enabled) { |
| node = resolveNode(node); |
| if (node != null) { |
| boolean oldValue = node.isEnabled(); |
| node.setEnabled(enabled, IDimensions.ENABLED_GRANTED); |
| boolean newValue = node.isEnabled(); |
| if (oldValue != newValue) { |
| fireNodesUpdated(node.getParentNode(), CollectionUtility.arrayList(node)); |
| } |
| } |
| } |
| |
| @Override |
| public void setNodeVisiblePermission(ITreeNode node, Permission permission) { |
| node = resolveNode(node); |
| if (node != null) { |
| AbstractTreeNode.setVisiblePermission(permission, node); |
| // don't 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 visible) { |
| node = resolveNode(node); |
| if (node != null) { |
| node.setVisible(visible, IDimensions.VISIBLE); |
| // don't fire observers since visibility change only has an effect when used in init method |
| } |
| } |
| |
| @Override |
| public void setNodeVisibleGranted(ITreeNode node, boolean visible) { |
| node = resolveNode(node); |
| if (node != null) { |
| node.setVisible(visible, IDimensions.VISIBLE_GRANTED); |
| // don't 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 && node.isLeaf() != b) { |
| node.setLeafInternal(b); |
| fireNodesUpdated(node.getParentNode(), CollectionUtility.arrayList(node)); |
| } |
| } |
| |
| @Override |
| public boolean isNodeChecked(ITreeNode node) { |
| if (node != null) { |
| return m_checkedNodes.contains(node); |
| } |
| else { |
| return false; |
| } |
| } |
| |
| @Override |
| public void setNodeChecked(ITreeNode node, boolean checked) { |
| setNodesChecked(CollectionUtility.arrayList(node), checked); |
| } |
| |
| @Override |
| public void setNodeChecked(ITreeNode node, boolean checked, boolean enabledNodesOnly) { |
| setNodesChecked(CollectionUtility.arrayList(node), checked, enabledNodesOnly); |
| } |
| |
| @Override |
| public void setNodesChecked(List<ITreeNode> nodes, boolean checked) { |
| setNodesChecked(nodes, checked, false); |
| } |
| |
| @Override |
| public void setNodesChecked(List<ITreeNode> nodes, boolean checked, boolean enabledNodesOnly) { |
| if (!isCheckable()) { |
| return; |
| } |
| List<ITreeNode> changedNodes = new ArrayList<>(); |
| for (ITreeNode node : nodes) { |
| node = resolveNode(node); |
| if (node != null && node.isChecked() != checked && (!enabledNodesOnly || node.isEnabled())) { |
| if (checked) { |
| m_checkedNodes.add(node); |
| } |
| else { |
| m_checkedNodes.remove(node); |
| } |
| changedNodes.add(node); |
| |
| //uncheck others in single-check mode |
| if (checked && !isMultiCheck()) { |
| for (Iterator<ITreeNode> it = m_checkedNodes.iterator(); it.hasNext();) { |
| if (it.next() != node) { |
| it.remove(); |
| } |
| } |
| break; |
| } |
| } |
| } |
| if (!changedNodes.isEmpty()) { |
| if (isAutoCheckChildNodes() && isMultiCheck()) { |
| if (m_currentParentNodes == null) { |
| m_currentParentNodes = nodes; |
| } |
| try { |
| interceptAutoCheckChildNodes(nodes, checked, enabledNodesOnly); |
| } |
| catch (RuntimeException ex) { |
| BEANS.get(ExceptionHandler.class).handle(ex); |
| } |
| if (nodes.equals(m_currentParentNodes)) { |
| m_currentParentNodes = null; |
| } |
| } |
| if (m_currentParentNodes == null) { |
| fireNodesChecked(changedNodes); |
| } |
| } |
| } |
| |
| /** |
| * Recursively checks/unchecks the subtree of <code>parent</code>. |
| */ |
| private void uncheckAllRec(ITreeNode parent, boolean b) { |
| if (parent == null) { |
| return; |
| } |
| setNodeChecked(parent, b); |
| for (ITreeNode node : parent.getChildNodes()) { |
| uncheckAllRec(node, b); |
| } |
| } |
| |
| @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 && node.getStatus() != status) { |
| node.setStatusInternal(status); |
| fireNodesUpdated(node.getParentNode(), CollectionUtility.arrayList(node)); |
| } |
| } |
| |
| 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) { |
| try { |
| setTreeChanging(true); |
| // |
| expandAllRec(parent, 0); |
| fireNodeExpandedRecursive(parent, true); |
| } |
| finally { |
| setTreeChanging(false); |
| } |
| } |
| |
| private void expandAllRec(ITreeNode parent, int level) { |
| setNodeExpanded(parent, true); |
| // loop detection |
| if (level >= 32) { |
| LOG.warn("detected loop on tree node {}", parent); |
| } |
| else { |
| List<ITreeNode> children = parent.getChildNodes(); |
| for (ITreeNode child : children) { |
| expandAllRec(child, level + 1); |
| } |
| } |
| } |
| |
| @Override |
| public void collapseAll(ITreeNode parent) { |
| try { |
| setTreeChanging(true); |
| // |
| List<ITreeNode> list = new ArrayList<>(); |
| fetchAllCollapsingNodesRec(parent, 0, list); |
| for (int n = list.size(), i = n - 1; i >= 0; i--) { |
| setNodeExpanded(list.get(i), false); |
| } |
| fireNodeExpandedRecursive(parent, 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); |
| } |
| List<ITreeNode> children = parent.getChildNodes(); |
| for (ITreeNode child : children) { |
| fetchAllCollapsingNodesRec(child, level + 1, list); |
| } |
| } |
| } |
| |
| @Override |
| public List<IKeyStroke> getKeyStrokes() { |
| return CollectionUtility.arrayList(getKeyStrokesInternal()); |
| } |
| |
| protected List<IKeyStroke> getKeyStrokesInternal() { |
| return propertySupport.<IKeyStroke>getPropertyList(PROP_KEY_STROKES); |
| } |
| |
| @Override |
| public void setKeyStrokes(List<? extends IKeyStroke> keyStrokes) { |
| m_baseKeyStrokes = CollectionUtility.arrayListWithoutNullElements(keyStrokes); |
| rebuildKeyStrokesInternal(); |
| } |
| |
| private void setKeyStrokesInternal(List<? extends IKeyStroke> keyStrokes) { |
| propertySupport.setPropertyList(PROP_KEY_STROKES, keyStrokes); |
| } |
| |
| /* |
| * modifications |
| */ |
| @Override |
| public void addChildNode(ITreeNode parent, ITreeNode child) { |
| if (child != null) { |
| addChildNodes(parent, CollectionUtility.arrayList(child)); |
| } |
| } |
| |
| @Override |
| public void addChildNode(int startIndex, ITreeNode parent, ITreeNode child) { |
| if (child != null) { |
| addChildNodes(startIndex, parent, CollectionUtility.arrayList(child)); |
| } |
| } |
| |
| @Override |
| public void addChildNodes(ITreeNode parent, List<? extends ITreeNode> children) { |
| addChildNodes(parent.getChildNodeCount(), parent, children); |
| } |
| |
| @Override |
| public void addChildNodes(int startIndex, ITreeNode parent, List<? extends ITreeNode> children) { |
| if (!CollectionUtility.hasElements(children)) { |
| return; |
| } |
| try { |
| setTreeChanging(true); |
| // |
| parent = resolveNode(parent); |
| if (parent == null) { |
| return; // wrong parent |
| } |
| List<ITreeNode> newChildren = new ArrayList<>(children); |
| // Fire NODES_INSERTED event before actually inserting the nodes, because during insertion, other events might occur (e.g. NODE_CHANGED in decorateCell()) |
| fireNodesInserted(parent, newChildren); |
| // |
| ((AbstractTreeNode) parent).addChildNodesInternal(startIndex, children, true); |
| // check if all children were added, or if some were revoked using |
| // visible=false in init (addNotify) phase. |
| newChildren.removeIf(child -> child.getParentNode() == null); |
| // decorate |
| decorateAffectedNodeCells(parent, newChildren); |
| // filter |
| int level = 0; |
| ITreeNode tmp = parent; |
| while (tmp != null) { |
| tmp = tmp.getParentNode(); |
| level++; |
| } |
| for (ITreeNode child : newChildren) { |
| applyNodeFiltersRecInternal(child, parent.isFilterAccepted(), level); |
| } |
| } |
| finally { |
| setTreeChanging(false); |
| } |
| } |
| |
| @Override |
| public void updateNode(ITreeNode node) { |
| if (node != null) { |
| updateChildNodes(node.getParentNode(), CollectionUtility.hashSet(node)); |
| } |
| } |
| |
| @Override |
| public void updateChildNodes(ITreeNode parent, Collection<? extends ITreeNode> children) { |
| try { |
| setTreeChanging(true); |
| // |
| parent = resolveNode(parent); |
| Collection<ITreeNode> resolvedChildren = resolveNodes(children); |
| decorateAffectedNodeCells(parent, resolvedChildren); |
| fireNodesUpdated(parent, resolvedChildren); |
| } |
| finally { |
| setTreeChanging(false); |
| } |
| } |
| |
| @Override |
| public void updateChildNodeOrder(ITreeNode parent, List<? extends ITreeNode> newChildren) { |
| try { |
| setTreeChanging(true); |
| // |
| parent = resolveNode(parent); |
| if (parent == null) { |
| return; // wrong parent |
| } |
| List<ITreeNode> newChildrenResolved = resolveNodes(newChildren); |
| if (!newChildren.isEmpty() && newChildrenResolved.size() == newChildren.size()) { |
| ((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, CollectionUtility.hashSet(child)); |
| } |
| |
| @Override |
| public void removeChildNodes(ITreeNode parent, Collection<? extends ITreeNode> children) { |
| if (!CollectionUtility.hasElements(children)) { |
| return; |
| } |
| try { |
| setTreeChanging(true); |
| // |
| parent = resolveNode(parent); |
| if (parent == null) { |
| return; |
| } |
| children = resolveNodes(children); |
| deselectNodes(children); |
| // remove children from set of checked nodes |
| for (ITreeNode child : children) { |
| uncheckAllRec(child, false); |
| } |
| ((AbstractTreeNode) parent).removeChildNodesInternal(children, true, isAutoDiscardOnDelete()); |
| decorateAffectedNodeCells(parent, parent.getChildNodes()); |
| if (!isAutoDiscardOnDelete()) { |
| for (ITreeNode child : children) { |
| if (child.getStatus() == ITreeNode.STATUS_INSERTED) { |
| // The node was new and now it is gone, so it can be disposed now |
| child.dispose(); |
| } |
| else { |
| // The node will be disposed later. |
| child.setStatusInternal(ITreeNode.STATUS_DELETED); |
| m_deletedNodes.put(child.getPrimaryKey(), child); |
| } |
| } |
| } |
| // filter |
| int level = 0; |
| ITreeNode tmp = parent; |
| while (tmp != null) { |
| tmp = tmp.getParentNode(); |
| level++; |
| } |
| for (ITreeNode child : parent.getChildNodes()) { |
| applyNodeFiltersRecInternal(child, parent.isFilterAccepted(), level); |
| } |
| if (parent.getChildNodeCount() == 0) { |
| fireAllChildNodesDeleted(parent, children); |
| } |
| else { |
| fireNodesDeleted(parent, children); |
| } |
| } |
| finally { |
| setTreeChanging(false); |
| } |
| } |
| |
| @Override |
| public void removeAllChildNodes(ITreeNode parent) { |
| if (parent != null) { |
| removeChildNodes(parent, parent.getChildNodes()); |
| } |
| } |
| |
| @Override |
| public void discardDeletedNode(ITreeNode node) { |
| discardDeletedNodes(CollectionUtility.arrayList(node)); |
| } |
| |
| @Override |
| public void discardDeletedNodes(Collection<ITreeNode> nodes) { |
| for (ITreeNode node : nodes) { |
| ITreeNode delNode = m_deletedNodes.get(node.getPrimaryKey()); |
| if (delNode == node) { |
| m_deletedNodes.remove(node.getPrimaryKey()); |
| } |
| } |
| } |
| |
| @Override |
| public void disposeDeletedNode(ITreeNode node) { |
| disposeDeletedNodes(CollectionUtility.arrayList(node)); |
| } |
| |
| @Override |
| public void disposeDeletedNodes(Collection<ITreeNode> nodes) { |
| for (ITreeNode node : CollectionUtility.arrayList(nodes)) { |
| ITreeNode delNode = m_deletedNodes.get(node.getPrimaryKey()); |
| if (delNode == node) { |
| node.setTreeInternal(null, true); |
| try { |
| node.dispose(); |
| } |
| catch (RuntimeException e) { |
| LOG.warn("Exception while disposing node: {}.", node, e); |
| } |
| discardDeletedNode(node); |
| } |
| } |
| } |
| |
| @Override |
| public void clearDeletedNodes() { |
| disposeDeletedNodes(m_deletedNodes.values()); |
| } |
| |
| @Override |
| public TreeVisitResult visitTree(IDepthFirstTreeVisitor<ITreeNode> v) { |
| return TreeUtility.visitNode(getRootNode(), v); |
| } |
| |
| @Override |
| public TreeVisitResult visitNode(ITreeNode node, IDepthFirstTreeVisitor<ITreeNode> v) { |
| return TreeUtility.visitNode(node, v); |
| } |
| |
| @Override |
| public TreeVisitResult visitVisibleTree(IDepthFirstTreeVisitor<ITreeNode> v) { |
| Function<ITreeNode, Collection<? extends ITreeNode>> childrenSupplier = ITreeNode::getFilteredChildNodes; |
| |
| if (isRootNodeVisible()) { |
| return TreeTraversals.create(v, childrenSupplier).traverse(getRootNode()); |
| } |
| |
| List<ITreeNode> visibleTopLevel = new ArrayList<>(childrenSupplier.apply(getRootNode())); |
| return TreeUtility.visitNodes(visibleTopLevel, v, childrenSupplier); |
| } |
| |
| @Override |
| public int getDeletedNodeCount() { |
| return m_deletedNodes.size(); |
| } |
| |
| @Override |
| public Set<ITreeNode> getDeletedNodes() { |
| return CollectionUtility.hashSet(m_deletedNodes.values()); |
| } |
| |
| @Override |
| public int getInsertedNodeCount() { |
| P_AbstractCountingTreeVisitor v = new P_AbstractCountingTreeVisitor() { |
| @Override |
| protected boolean accept(ITreeNode node) { |
| return node.isStatusInserted(); |
| } |
| }; |
| visitNode(getRootNode(), v); |
| return v.getCount(); |
| } |
| |
| @Override |
| public Set<ITreeNode> getInsertedNodes() { |
| CollectingVisitor<ITreeNode> v = new CollectingVisitor<ITreeNode>() { |
| @Override |
| protected boolean accept(ITreeNode element) { |
| return element.isStatusInserted(); |
| } |
| }; |
| visitNode(getRootNode(), v); |
| return CollectionUtility.hashSet(v.getCollection()); |
| } |
| |
| @Override |
| public int getUpdatedNodeCount() { |
| P_AbstractCountingTreeVisitor v = new P_AbstractCountingTreeVisitor() { |
| @Override |
| protected boolean accept(ITreeNode node) { |
| return node.isStatusUpdated(); |
| } |
| }; |
| visitNode(getRootNode(), v); |
| return v.getCount(); |
| } |
| |
| @Override |
| public Set<ITreeNode> getUpdatedNodes() { |
| CollectingVisitor<ITreeNode> v = new CollectingVisitor<ITreeNode>() { |
| @Override |
| protected boolean accept(ITreeNode element) { |
| return element.isStatusUpdated(); |
| } |
| }; |
| visitNode(getRootNode(), v); |
| return CollectionUtility.hashSet(v.getCollection()); |
| } |
| |
| @Override |
| public int getSelectedNodeCount() { |
| return m_selectedNodes.size(); |
| } |
| |
| @Override |
| public ITreeNode getSelectedNode() { |
| if (!m_selectedNodes.isEmpty()) { |
| return m_selectedNodes.iterator().next(); |
| } |
| else { |
| return null; |
| } |
| } |
| |
| @Override |
| public Set<ITreeNode> getSelectedNodes() { |
| return CollectionUtility.hashSet(m_selectedNodes); |
| } |
| |
| @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(CollectionUtility.hashSet(node), append); |
| } |
| else { |
| selectNodes(null, append); |
| } |
| } |
| |
| @Override |
| public void selectNodes(Collection<? extends ITreeNode> nodes, boolean append) { |
| nodes = resolveNodes(nodes); |
| if (nodes == null) { |
| nodes = CollectionUtility.hashSet(); |
| } |
| Set<ITreeNode> newSelection = new HashSet<>(); |
| if (append) { |
| newSelection.addAll(m_selectedNodes); |
| newSelection.addAll(nodes); |
| } |
| else { |
| newSelection.addAll(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(nodes)) { |
| // ok |
| } |
| else { |
| Set<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> next = new Holder<>(ITreeNode.class); |
| IDepthFirstTreeVisitor<ITreeNode> v = new DepthFirstTreeVisitor<ITreeNode>() { |
| boolean m_foundCurrent; |
| |
| @Override |
| public TreeVisitResult preVisit(ITreeNode element, int level, int index) { |
| if (m_foundCurrent) { |
| if (element.isFilterAccepted()) { |
| next.setValue(element); |
| return TreeVisitResult.TERMINATE; |
| } |
| } |
| else { |
| m_foundCurrent = element == current; |
| } |
| return TreeVisitResult.CONTINUE; |
| } |
| }; |
| visitVisibleTree(v); |
| if (next.getValue() != null) { |
| selectNode(next.getValue()); |
| } |
| } |
| else { |
| selectFirstNode(); |
| } |
| } |
| |
| @Override |
| public void selectPreviousNode() { |
| final ITreeNode current = getSelectedNode(); |
| if (current != null) { |
| final Holder<ITreeNode> foundVisited = new Holder<>(ITreeNode.class); |
| IDepthFirstTreeVisitor<ITreeNode> v = new DepthFirstTreeVisitor<ITreeNode>() { |
| @Override |
| public TreeVisitResult preVisit(ITreeNode element, int level, int index) { |
| if (element == current) { |
| return TreeVisitResult.TERMINATE; |
| } |
| if (element.isFilterAccepted()) { |
| foundVisited.setValue(element); |
| } |
| return TreeVisitResult.CONTINUE; |
| } |
| }; |
| visitVisibleTree(v); |
| if (foundVisited.getValue() != null) { |
| selectNode(foundVisited.getValue()); |
| } |
| } |
| else { |
| selectLastNode(); |
| } |
| } |
| |
| @Override |
| public ITreeNode selectFirstNode() { |
| if (!isRootNodeVisible()) { |
| getRootNode().ensureChildrenLoaded(); |
| } |
| |
| final Holder<ITreeNode> foundVisited = new Holder<>(ITreeNode.class); |
| IDepthFirstTreeVisitor<ITreeNode> v = new DepthFirstTreeVisitor<ITreeNode>() { |
| @Override |
| public TreeVisitResult preVisit(ITreeNode element, int level, int index) { |
| if (element.isFilterAccepted()) { |
| foundVisited.setValue(element); |
| return TreeVisitResult.TERMINATE; |
| } |
| return TreeVisitResult.CONTINUE; |
| } |
| }; |
| visitVisibleTree(v); |
| |
| ITreeNode firstNode = foundVisited.getValue(); |
| if (firstNode != null) { |
| selectNode(firstNode); |
| } |
| return firstNode; |
| } |
| |
| @Override |
| public void selectLastNode() { |
| if (!isRootNodeVisible()) { |
| try { |
| getRootNode().ensureChildrenLoaded(); |
| } |
| catch (RuntimeException e) { |
| BEANS.get(ExceptionHandler.class).handle(e); |
| } |
| } |
| final Holder<ITreeNode> foundVisited = new Holder<>(ITreeNode.class); |
| IDepthFirstTreeVisitor<ITreeNode> v = new DepthFirstTreeVisitor<ITreeNode>() { |
| @Override |
| public TreeVisitResult preVisit(ITreeNode element, int level, int index) { |
| if (element.isFilterAccepted()) { |
| foundVisited.setValue(element); |
| } |
| return TreeVisitResult.CONTINUE; |
| } |
| }; |
| 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()) && parent.isFilterAccepted()) { |
| selectNode(parent); |
| return; |
| } |
| // |
| parent = parent.getParentNode(); |
| } |
| } |
| else { |
| selectFirstNode(); |
| } |
| } |
| |
| @Override |
| public void deselectNode(ITreeNode node) { |
| if (node != null) { |
| deselectNodes(CollectionUtility.hashSet(node)); |
| } |
| else { |
| deselectNodes(null); |
| } |
| } |
| |
| @Override |
| public void deselectNodes(Collection<? extends ITreeNode> nodes) { |
| nodes = resolveNodes(nodes); |
| if (CollectionUtility.hasElements(nodes)) { |
| Set<ITreeNode> oldSelection = new HashSet<>(m_selectedNodes); |
| Set<ITreeNode> newSelection = new HashSet<>(); |
| 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 Set<ITreeNode> getCheckedNodes() { |
| return CollectionUtility.hashSet(m_checkedNodes); |
| } |
| |
| @Override |
| public int getCheckedNodesCount() { |
| return m_checkedNodes.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 == null) { |
| return null; |
| } |
| if (node.getTree() == this) { |
| return node; |
| } |
| return null; |
| } |
| |
| /** |
| * Keeps order of input. |
| */ |
| private List<ITreeNode> resolveNodes(Collection<? extends ITreeNode> nodes) { |
| if (!CollectionUtility.hasElements(nodes)) { |
| return CollectionUtility.emptyArrayList(); |
| } |
| List<ITreeNode> resolvedNodes = new ArrayList<>(nodes.size()); |
| for (ITreeNode node : nodes) { |
| if (resolveNode(node) != null) { |
| resolvedNodes.add(node); |
| } |
| } |
| return resolvedNodes; |
| } |
| |
| @Override |
| public TreeListeners treeListeners() { |
| return m_listeners; |
| } |
| |
| protected IEventHistory<TreeEvent> createEventHistory() { |
| return new DefaultTreeEventHistory(5000L); |
| } |
| |
| @Override |
| public IEventHistory<TreeEvent> getEventHistory() { |
| return m_eventHistory; |
| } |
| |
| protected void filterInitializingTreeNodes(Collection<? extends ITreeNode> nodes) { |
| if (nodes == null) { |
| return; |
| } |
| nodes.removeIf(node -> node != null && node.isInitializing()); |
| } |
| |
| private void fireNodesInserted(ITreeNode parent, List<ITreeNode> children) { |
| if (parent != null && parent.isInitializing()) { |
| return; |
| } |
| filterInitializingTreeNodes(children); |
| if (CollectionUtility.hasElements(children)) { |
| fireTreeEventInternal(new TreeEvent(this, TreeEvent.TYPE_NODES_INSERTED, parent, children)); |
| } |
| } |
| |
| private void fireNodesUpdated(ITreeNode parent, Collection<ITreeNode> children) { |
| if (parent != null && parent.isInitializing()) { |
| return; |
| } |
| filterInitializingTreeNodes(children); |
| if (CollectionUtility.hasElements(children)) { |
| fireTreeEventInternal(new TreeEvent(this, TreeEvent.TYPE_NODES_UPDATED, parent, children)); |
| } |
| } |
| |
| private void fireNodesChecked(List<ITreeNode> nodes) { |
| filterInitializingTreeNodes(nodes); |
| if (CollectionUtility.hasElements(nodes)) { |
| fireTreeEventInternal(new TreeEvent(this, TreeEvent.TYPE_NODES_CHECKED, nodes)); |
| } |
| } |
| |
| @Override |
| public void fireNodeChanged(ITreeNode node) { |
| if (node != null && node.isInitializing()) { |
| return; |
| } |
| fireTreeEventInternal(new TreeEvent(this, TreeEvent.TYPE_NODE_CHANGED, node)); |
| } |
| |
| private void fireNodeFilterChanged() { |
| if (getRootNode() != null && getRootNode().isInitializing()) { |
| return; |
| } |
| fireTreeEventInternal(new TreeEvent(this, TreeEvent.TYPE_NODE_FILTER_CHANGED, getRootNode())); |
| } |
| |
| private void fireNodesDeleted(ITreeNode parent, Collection<? extends ITreeNode> children) { |
| if (parent != null && parent.isInitializing()) { |
| return; |
| } |
| filterInitializingTreeNodes(children); |
| if (CollectionUtility.hasElements(children)) { |
| fireTreeEventInternal(new TreeEvent(this, TreeEvent.TYPE_NODES_DELETED, parent, children)); |
| } |
| } |
| |
| private void fireAllChildNodesDeleted(ITreeNode parent, Collection<? extends ITreeNode> children) { |
| if (parent != null && parent.isInitializing()) { |
| return; |
| } |
| filterInitializingTreeNodes(children); |
| if (CollectionUtility.hasElements(children)) { |
| fireTreeEventInternal(new TreeEvent(this, TreeEvent.TYPE_ALL_CHILD_NODES_DELETED, parent, children)); |
| } |
| } |
| |
| private void fireChildNodeOrderChanged(ITreeNode parent, List<? extends ITreeNode> children) { |
| if (parent != null && parent.isInitializing()) { |
| return; |
| } |
| filterInitializingTreeNodes(children); |
| if (CollectionUtility.hasElements(children)) { |
| fireTreeEventInternal(new TreeEvent(this, TreeEvent.TYPE_CHILD_NODE_ORDER_CHANGED, parent, children)); |
| } |
| } |
| |
| private void fireBeforeNodesSelected(Set<ITreeNode> oldSelection, Set<ITreeNode> newSelection) { |
| TreeEvent e = new TreeEvent(this, TreeEvent.TYPE_BEFORE_NODES_SELECTED, newSelection); |
| Set<ITreeNode> deselectedNodes = new HashSet<>(oldSelection); |
| deselectedNodes.removeAll(newSelection); |
| e.setDeselectedNodes(deselectedNodes); |
| Set<ITreeNode> newSelectedNodes = new HashSet<>(newSelection); |
| newSelectedNodes.removeAll(oldSelection); |
| |
| boolean emptySelection = newSelectedNodes.isEmpty(); |
| filterInitializingTreeNodes(newSelectedNodes); |
| if (!emptySelection && newSelectedNodes.isEmpty()) { |
| return; |
| } |
| |
| e.setNewSelectedNodes(newSelectedNodes); |
| fireTreeEventInternal(e); |
| } |
| |
| private void fireNodesSelected(Set<ITreeNode> oldSelection, Set<ITreeNode> newSelection) { |
| // single observer: rebuild title |
| if (isAutoTitle()) { |
| rebuildTitleInternal(); |
| } |
| // fire |
| TreeEvent e = new TreeEvent(this, TreeEvent.TYPE_NODES_SELECTED, newSelection); |
| Set<ITreeNode> deselectedNodes = new HashSet<>(oldSelection); |
| deselectedNodes.removeAll(newSelection); |
| e.setDeselectedNodes(deselectedNodes); |
| Set<ITreeNode> newSelectedNodes = new HashSet<>(newSelection); |
| newSelectedNodes.removeAll(oldSelection); |
| |
| boolean emptySelection = newSelectedNodes.isEmpty(); |
| filterInitializingTreeNodes(newSelectedNodes); |
| if (!emptySelection && newSelectedNodes.isEmpty()) { |
| return; |
| } |
| |
| e.setNewSelectedNodes(newSelectedNodes); |
| //single observer |
| try { |
| nodesSelectedInternal(deselectedNodes, newSelectedNodes); |
| interceptNodesSelected(e); |
| } |
| catch (Exception ex) { |
| BEANS.get(ExceptionHandler.class).handle(ex); |
| } |
| //end single observer |
| fireTreeEventInternal(e); |
| } |
| |
| protected void nodesSelectedInternal(Set<ITreeNode> oldSelection, Set<ITreeNode> newSelection) { |
| updateNodeMenus(m_selectedNodes); |
| } |
| |
| protected void updateNodeMenus(Set<ITreeNode> newSelectedNodes) { |
| // remove old |
| if (m_currentNodeMenus != null) { |
| getContextMenu().removeChildActions(m_currentNodeMenus); |
| m_currentNodeMenus = null; |
| } |
| List<IMenu> nodeMenus = new ArrayList<>(); |
| // take only first node to avoid having multiple same menus due to all nodes. |
| if (CollectionUtility.hasElements(newSelectedNodes)) { |
| nodeMenus.addAll(CollectionUtility.firstElement(newSelectedNodes).getMenus()); |
| m_currentNodeMenus = nodeMenus; |
| getContextMenu().addChildActions(nodeMenus); |
| } |
| } |
| |
| private void fireNodeExpanded(ITreeNode node, boolean b) { |
| if (node != null && !node.isInitializing()) { |
| if (b) { |
| fireTreeEventInternal(new TreeEvent(this, TreeEvent.TYPE_NODE_EXPANDED, node)); |
| } |
| else { |
| fireTreeEventInternal(new TreeEvent(this, TreeEvent.TYPE_NODE_COLLAPSED, node)); |
| } |
| } |
| } |
| |
| private void fireNodeExpandedRecursive(ITreeNode node, boolean b) { |
| if (node != null && !node.isInitializing()) { |
| if (b) { |
| fireTreeEventInternal(new TreeEvent(this, TreeEvent.TYPE_NODE_EXPANDED_RECURSIVE, node)); |
| } |
| else { |
| fireTreeEventInternal(new TreeEvent(this, TreeEvent.TYPE_NODE_COLLAPSED_RECURSIVE, node)); |
| } |
| } |
| } |
| |
| private void fireNodeClick(ITreeNode node, MouseButton mouseButton) { |
| if (node != null && !node.isInitializing()) { |
| try { |
| interceptNodeClick(node, mouseButton); |
| } |
| catch (Exception ex) { |
| BEANS.get(ExceptionHandler.class).handle(ex); |
| } |
| } |
| } |
| |
| protected void interceptNodesChecked(List<ITreeNode> nodes) { |
| List<? extends ITreeExtension<? extends AbstractTree>> extensions = getAllExtensions(); |
| TreeNodesCheckedChain chain = new TreeNodesCheckedChain(extensions); |
| chain.execNodesChecked(nodes); |
| } |
| |
| protected void interceptAutoCheckChildNodes(List<ITreeNode> nodes, boolean checked, boolean enabledNodesOnly) { |
| List<? extends ITreeExtension<? extends AbstractTree>> extensions = getAllExtensions(); |
| TreeAutoCheckChildNodesChain chain = new TreeAutoCheckChildNodesChain(extensions); |
| chain.execAutoCheckChildNodes(nodes, checked, enabledNodesOnly); |
| } |
| |
| private void fireNodeAction(ITreeNode node) { |
| if (isActionRunning()) { |
| return; |
| } |
| if (node == null || node.isInitializing() || !node.isLeaf()) { |
| return; |
| } |
| |
| try { |
| setActionRunning(true); |
| try { |
| interceptNodeAction(node); |
| } |
| catch (Exception ex) { |
| BEANS.get(ExceptionHandler.class).handle(ex); |
| } |
| } |
| finally { |
| setActionRunning(false); |
| } |
| } |
| |
| private void fireRequestFocus() { |
| TreeEvent e = new TreeEvent(this, TreeEvent.TYPE_REQUEST_FOCUS); |
| fireTreeEventInternal(e); |
| } |
| |
| private TransferObject fireNodesDragRequest(Collection<ITreeNode> nodes) { |
| filterInitializingTreeNodes(nodes); |
| if (CollectionUtility.hasElements(nodes)) { |
| 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) { |
| if (node != null && node.isInitializing()) { |
| return; |
| } |
| TreeEvent e = new TreeEvent(this, TreeEvent.TYPE_NODE_DROP_ACTION, node); |
| e.setDropObject(dropData); |
| fireTreeEventInternal(e); |
| } |
| |
| /** |
| * This method gets called when the drop node is changed, e.g. the dragged object is moved over a new drop target. |
| * |
| * @since 4.0-M7 |
| */ |
| public void fireNodeDropTargetChanged(ITreeNode node) { |
| if (node != null && node.isInitializing()) { |
| return; |
| } |
| TreeEvent e = new TreeEvent(this, TreeEvent.TYPE_NODE_DROP_TARGET_CHANGED, node); |
| fireTreeEventInternal(e); |
| } |
| |
| /** |
| * This method gets called after the drag action has been finished. |
| * |
| * @since 4.0-M7 |
| */ |
| public void fireDragFinished() { |
| TreeEvent e = new TreeEvent(this, TreeEvent.TYPE_DRAG_FINISHED); |
| fireTreeEventInternal(e); |
| } |
| |
| private void fireNodeEnsureVisible(ITreeNode node) { |
| if (node != null && node.isInitializing()) { |
| return; |
| } |
| 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 |
| getEventBuffer().add(e); |
| } |
| else { |
| doFireTreeEvent(e); |
| } |
| } |
| |
| protected void doFireTreeEvent(TreeEvent e) { |
| m_listeners.fireEvent(e); |
| } |
| |
| /** |
| * add cells on the path to root and on children to decoration buffer |
| */ |
| private void decorateAffectedNodeCells(ITreeNode parent, Collection<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_processTreeBufferLoopDetection; |
| |
| /** |
| * 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 processTreeBuffers() { |
| //loop detection |
| try { |
| m_processTreeBufferLoopDetection++; |
| if (m_processTreeBufferLoopDetection > 100) { |
| LOG.error("LOOP DETECTION in {}. see stack trace for more details.", getClass(), new Exception("LOOP DETECTION")); |
| return; |
| } |
| processDecorationBuffer(); |
| processEventBuffer(); |
| } |
| finally { |
| m_processTreeBufferLoopDetection--; |
| } |
| } |
| |
| /** |
| * update row decorations |
| */ |
| private void processDecorationBuffer() { |
| if (!m_nodeDecorationBuffer.isEmpty()) { |
| Set<ITreeNode> set = m_nodeDecorationBuffer; |
| m_nodeDecorationBuffer = new HashSet<>(); |
| try { |
| setTreeChanging(true); |
| for (ITreeNode node : set) { |
| if (node.getTree() != null) { |
| try { |
| interceptDecorateCell(node, node.getCellForUpdate()); |
| } |
| catch (Exception t) { |
| LOG.warn("node {} ({})", node.getClass(), node.getCell().getText(), t); |
| } |
| } |
| } |
| } |
| finally { |
| setTreeChanging(false); |
| } |
| } |
| } |
| |
| /** |
| * fire events tree changes are finished now, fire all buffered events and call lookups |
| */ |
| private void processEventBuffer() { |
| if (!getEventBuffer().isEmpty()) { |
| final List<TreeEvent> list = getEventBuffer().consumeAndCoalesceEvents(); |
| // 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); |
| // |
| m_listeners.fireEvents(list); |
| } |
| finally { |
| setTreeChanging(false); |
| } |
| } |
| } |
| |
| @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) { |
| try { |
| setTreeChanging(true); |
| // |
| setNodeExpanded(node, false); |
| removeAllChildNodes(node); |
| node.setChildrenLoaded(false); |
| } |
| finally { |
| setTreeChanging(false); |
| } |
| } |
| |
| @Override |
| public void doAppLinkAction(String ref) { |
| if (isActionRunning()) { |
| return; |
| } |
| |
| try { |
| setActionRunning(true); |
| interceptAppLinkAction(ref); |
| } |
| finally { |
| setActionRunning(false); |
| } |
| } |
| |
| @Override |
| public void exportTreeData(final AbstractTreeFieldData target) { |
| exportTreeNodeDataRec(getRootNode().getChildNodes(), target, null); |
| } |
| |
| private void exportTreeNodeDataRec(List<ITreeNode> nodes, AbstractTreeFieldData treeData, TreeNodeData parentNodeData) { |
| List<TreeNodeData> nodeDataList = new ArrayList<>(nodes.size()); |
| 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) { |
| TreeNodeData nodeData = new TreeNodeData(); |
| return nodeData; |
| } |
| |
| @Override |
| public void importTreeData(AbstractTreeFieldData source) { |
| 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) { |
| 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 node to the tree. |
| */ |
| protected ITreeNode importTreeNodeData(ITreeNode parentNode, AbstractTreeFieldData treeData, TreeNodeData nodeData) { |
| return null; |
| } |
| |
| protected ITreeUIFacade createUIFacade() { |
| return new P_UIFacade(); |
| } |
| |
| @Override |
| public ITreeUIFacade getUIFacade() { |
| return m_uiFacade; |
| } |
| |
| private boolean isActionRunning() { |
| return FLAGS_BIT_HELPER.isBitSet(ACTION_RUNNING, m_flags); |
| } |
| |
| private void setActionRunning(boolean b) { |
| m_flags = FLAGS_BIT_HELPER.changeBit(ACTION_RUNNING, b, m_flags); |
| } |
| |
| @Override |
| public boolean isSaveAndRestoreScrollbars() { |
| return FLAGS_BIT_HELPER.isBitSet(SAVE_AND_RESTORE_SCROLLBARS, m_flags); |
| } |
| |
| @Override |
| public void setSaveAndRestoreScrollbars(boolean b) { |
| m_flags = FLAGS_BIT_HELPER.changeBit(SAVE_AND_RESTORE_SCROLLBARS, b, m_flags); |
| } |
| |
| private abstract static class P_AbstractCountingTreeVisitor extends DepthFirstTreeVisitor<ITreeNode> { |
| |
| private int m_count; |
| |
| @Override |
| public TreeVisitResult preVisit(ITreeNode element, int level, int index) { |
| if (accept(element)) { |
| m_count++; |
| } |
| return TreeVisitResult.CONTINUE; |
| } |
| |
| protected boolean accept(ITreeNode node) { |
| return true; |
| } |
| |
| public int getCount() { |
| return m_count; |
| } |
| }// end private class |
| |
| /* |
| * UI Notifications |
| */ |
| protected 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 setNodesCheckedFromUI(List<ITreeNode> nodes, boolean checked) { |
| if (!isEnabled()) { |
| return; |
| } |
| try { |
| pushUIProcessor(); |
| try { |
| setTreeChanging(true); |
| nodes = resolveNodes(nodes); |
| if (!nodes.isEmpty()) { |
| setNodesChecked(nodes, checked, true); |
| } |
| } |
| finally { |
| setTreeChanging(false); |
| } |
| } |
| catch (RuntimeException e) { |
| StringBuilder msg = new StringBuilder(); |
| for (ITreeNode node : nodes) { |
| msg.append("["); |
| msg.append(node.getCell().toPlainText()); |
| msg.append("]"); |
| } |
| throw BEANS.get(PlatformExceptionTranslator.class).translate(e) |
| .withContextInfo("nodes", msg.toString()); |
| } |
| finally { |
| popUIProcessor(); |
| } |
| } |
| |
| @Override |
| public void setNodeExpandedFromUI(ITreeNode node, boolean on, boolean lazy) { |
| try { |
| pushUIProcessor(); |
| try { |
| setTreeChanging(true); |
| node = resolveNode(node); |
| if (node != null && (node.isExpanded() != on || node.isExpandedLazy() != lazy)) { |
| if (on && (node.isChildrenDirty() || node.isChildrenVolatile())) { |
| node.loadChildren(); |
| } |
| setNodeExpanded(node, on, lazy); |
| } |
| } |
| finally { |
| setTreeChanging(false); |
| } |
| } |
| catch (RuntimeException e) { |
| if (node != null) { |
| throw BEANS.get(PlatformExceptionTranslator.class).translate(e) |
| .withContextInfo("node", node.getCell().toPlainText()); |
| } |
| throw e; |
| } |
| finally { |
| popUIProcessor(); |
| } |
| } |
| |
| @Override |
| public void setNodeSelectedAndExpandedFromUI(ITreeNode node) { |
| try { |
| pushUIProcessor(); |
| try { |
| setTreeChanging(true); |
| node = resolveNode(node); |
| if (node != null) { |
| if (node.isChildrenDirty() || node.isChildrenVolatile()) { |
| node.loadChildren(); |
| } |
| setNodeExpanded(node, true); |
| selectNode(node, false); |
| if (!isScrollToSelection()) { |
| scrollToSelection(); |
| } |
| } |
| } |
| finally { |
| setTreeChanging(false); |
| } |
| } |
| catch (RuntimeException e) { |
| if (node != null) { |
| throw BEANS.get(PlatformExceptionTranslator.class).translate(e) |
| .withContextInfo("cell", node.getCell().toPlainText()); |
| } |
| throw e; |
| } |
| finally { |
| popUIProcessor(); |
| } |
| } |
| |
| @Override |
| public void setNodesSelectedFromUI(List<ITreeNode> nodes) { |
| try { |
| pushUIProcessor(); |
| try { |
| setTreeChanging(true); |
| |
| List<ITreeNode> validNodes = resolveNodes(nodes); |
| |
| // remove filtered (invisible) nodes from selection |
| validNodes.removeIf(iTreeNode -> !iTreeNode.isFilterAccepted()); |
| |
| // load children for selection |
| for (ITreeNode node : validNodes) { |
| if (node.isChildrenLoaded() && (node.isChildrenDirty() || node.isChildrenVolatile())) { |
| node.loadChildren(); |
| } |
| } |
| |
| selectNodes(validNodes, false); |
| } |
| finally { |
| setTreeChanging(false); |
| } |
| } |
| catch (RuntimeException e) { |
| if (nodes != null) { |
| throw BEANS.get(PlatformExceptionTranslator.class).translate(e) |
| .withContextInfo("nodes", nodes.toString()); |
| } |
| throw e; |
| } |
| finally { |
| popUIProcessor(); |
| } |
| } |
| |
| @Override |
| public void fireNodeClickFromUI(ITreeNode node, MouseButton mouseButton) { |
| try { |
| pushUIProcessor(); |
| node = resolveNode(node); |
| if (node != null) { |
| fireNodeClick(node, mouseButton); |
| } |
| } |
| finally { |
| popUIProcessor(); |
| } |
| } |
| |
| @Override |
| public void fireNodeActionFromUI(ITreeNode node) { |
| try { |
| pushUIProcessor(); |
| node = resolveNode(node); |
| if (node != null) { |
| fireNodeAction(node); |
| } |
| } |
| finally { |
| popUIProcessor(); |
| } |
| } |
| |
| @Override |
| public TransferObject fireNodesDragRequestFromUI() { |
| try { |
| pushUIProcessor(); |
| Collection<ITreeNode> nodes = getSelectedNodes(); |
| return fireNodesDragRequest(nodes); |
| } |
| finally { |
| popUIProcessor(); |
| } |
| } |
| |
| @Override |
| public void fireDragFinishedFromUI() { |
| try { |
| pushUIProcessor(); |
| fireDragFinished(); |
| } |
| finally { |
| popUIProcessor(); |
| } |
| } |
| |
| @Override |
| public void fireNodeDropTargetChangedFromUI(ITreeNode node) { |
| try { |
| pushUIProcessor(); |
| node = resolveNode(node); |
| if (node != null) { |
| fireNodeDropTargetChanged(node); |
| } |
| } |
| finally { |
| popUIProcessor(); |
| } |
| } |
| |
| @Override |
| public void fireNodeDropActionFromUI(ITreeNode node, TransferObject dropData) { |
| try { |
| pushUIProcessor(); |
| node = resolveNode(node); |
| fireNodeDropAction(node, dropData); |
| } |
| finally { |
| popUIProcessor(); |
| } |
| } |
| |
| @Override |
| public void fireAppLinkActionFromUI(String ref) { |
| try { |
| pushUIProcessor(); |
| doAppLinkAction(ref); |
| } |
| finally { |
| popUIProcessor(); |
| } |
| } |
| |
| @Override |
| public void setDisplayStyleFromUI(String style) { |
| setDisplayStyle(style); |
| } |
| |
| }// end private class |
| |
| /** |
| * The extension delegating to the local methods. This Extension is always at the end of the chain and will not call |
| * any further chain elements. |
| */ |
| protected static class LocalTreeExtension<OWNER extends AbstractTree> extends AbstractExtension<OWNER> implements ITreeExtension<OWNER> { |
| |
| public LocalTreeExtension(OWNER owner) { |
| super(owner); |
| } |
| |
| @Override |
| public void execDrop(TreeDropChain chain, ITreeNode node, TransferObject t) { |
| getOwner().execDrop(node, t); |
| } |
| |
| @Override |
| public void execInitTree(TreeInitTreeChain chain) { |
| getOwner().execInitTree(); |
| } |
| |
| @Override |
| public void execDropTargetChanged(TreeDropTargetChangedChain chain, ITreeNode node) { |
| getOwner().execDropTargetChanged(node); |
| } |
| |
| @Override |
| public TransferObject execDrag(TreeDragNodesChain chain, Collection<ITreeNode> nodes) { |
| return getOwner().execDrag(nodes); |
| } |
| |
| @Override |
| public void execNodeAction(TreeNodeActionChain chain, ITreeNode node) { |
| getOwner().execNodeAction(node); |
| } |
| |
| @Override |
| public void execNodeClick(TreeNodeClickChain chain, ITreeNode node, MouseButton mouseButton) { |
| getOwner().execNodeClick(node, mouseButton); |
| } |
| |
| @Override |
| public void execAppLinkAction(TreeHyperlinkActionChain chain, String ref) { |
| getOwner().execAppLinkAction(ref); |
| } |
| |
| @Override |
| public void execNodesSelected(TreeNodesSelectedChain chain, TreeEvent e) { |
| getOwner().execNodesSelected(e); |
| } |
| |
| @Override |
| public void execDisposeTree(TreeDisposeTreeChain chain) { |
| getOwner().execDisposeTree(); |
| } |
| |
| @Override |
| public void execDecorateCell(TreeDecorateCellChain chain, ITreeNode node, Cell cell) { |
| getOwner().execDecorateCell(node, cell); |
| } |
| |
| @Override |
| public TransferObject execDrag(TreeDragNodeChain chain, ITreeNode node) { |
| return getOwner().execDrag(node); |
| } |
| |
| @Override |
| public void execNodesChecked(TreeNodesCheckedChain chain, List<ITreeNode> nodes) { |
| getOwner().execNodesChecked(nodes); |
| } |
| |
| @Override |
| public void execAutoCheckChildNodes(TreeAutoCheckChildNodesChain chain, List<ITreeNode> nodes, boolean checked, boolean enabledNodesOnly) { |
| getOwner().execAutoCheckChildNodes(nodes, checked, enabledNodesOnly); |
| } |
| } |
| |
| protected final void interceptDrop(ITreeNode node, TransferObject t) { |
| List<? extends ITreeExtension<? extends AbstractTree>> extensions = getAllExtensions(); |
| TreeDropChain chain = new TreeDropChain(extensions); |
| chain.execDrop(node, t); |
| } |
| |
| protected final void interceptInitTree() { |
| List<? extends ITreeExtension<? extends AbstractTree>> extensions = getAllExtensions(); |
| TreeInitTreeChain chain = new TreeInitTreeChain(extensions); |
| chain.execInitTree(); |
| } |
| |
| protected final void interceptDropTargetChanged(ITreeNode node) { |
| List<? extends ITreeExtension<? extends AbstractTree>> extensions = getAllExtensions(); |
| TreeDropTargetChangedChain chain = new TreeDropTargetChangedChain(extensions); |
| chain.execDropTargetChanged(node); |
| } |
| |
| protected final TransferObject interceptDrag(Collection<ITreeNode> nodes) { |
| List<? extends ITreeExtension<? extends AbstractTree>> extensions = getAllExtensions(); |
| TreeDragNodesChain chain = new TreeDragNodesChain(extensions); |
| return chain.execDrag(nodes); |
| } |
| |
| protected final void interceptNodeAction(ITreeNode node) { |
| List<? extends ITreeExtension<? extends AbstractTree>> extensions = getAllExtensions(); |
| TreeNodeActionChain chain = new TreeNodeActionChain(extensions); |
| chain.execNodeAction(node); |
| } |
| |
| protected final void interceptNodeClick(ITreeNode node, MouseButton mouseButton) { |
| List<? extends ITreeExtension<? extends AbstractTree>> extensions = getAllExtensions(); |
| TreeNodeClickChain chain = new TreeNodeClickChain(extensions); |
| chain.execNodeClick(node, mouseButton); |
| } |
| |
| protected final void interceptAppLinkAction(String ref) { |
| List<? extends ITreeExtension<? extends AbstractTree>> extensions = getAllExtensions(); |
| TreeHyperlinkActionChain chain = new TreeHyperlinkActionChain(extensions); |
| chain.execHyperlinkAction(ref); |
| } |
| |
| protected final void interceptNodesSelected(TreeEvent e) { |
| List<? extends ITreeExtension<? extends AbstractTree>> extensions = getAllExtensions(); |
| TreeNodesSelectedChain chain = new TreeNodesSelectedChain(extensions); |
| chain.execNodesSelected(e); |
| } |
| |
| protected final void interceptDisposeTree() { |
| List<? extends ITreeExtension<? extends AbstractTree>> extensions = getAllExtensions(); |
| TreeDisposeTreeChain chain = new TreeDisposeTreeChain(extensions); |
| chain.execDisposeTree(); |
| } |
| |
| protected final void interceptDecorateCell(ITreeNode node, Cell cell) { |
| List<? extends ITreeExtension<? extends AbstractTree>> extensions = getAllExtensions(); |
| TreeDecorateCellChain chain = new TreeDecorateCellChain(extensions); |
| chain.execDecorateCell(node, cell); |
| } |
| |
| protected final TransferObject interceptDrag(ITreeNode node) { |
| List<? extends ITreeExtension<? extends AbstractTree>> extensions = getAllExtensions(); |
| TreeDragNodeChain chain = new TreeDragNodeChain(extensions); |
| return chain.execDrag(node); |
| } |
| } |