/*
* Copyright (c) 2011, grossmann
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the jo-widgets.org nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL jo-widgets.org BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
package org.jowidgets.impl.widgets.basic;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import org.jowidgets.api.controller.IDisposeListener;
import org.jowidgets.api.controller.ITreeNodeCheckableListener;
import org.jowidgets.api.controller.ITreeSelectionEvent;
import org.jowidgets.api.controller.ITreeSelectionListener;
import org.jowidgets.api.model.item.IMenuModel;
import org.jowidgets.api.toolkit.Toolkit;
import org.jowidgets.api.types.CheckedState;
import org.jowidgets.api.types.TreeAutoCheckPolicy;
import org.jowidgets.api.widgets.IPopupMenu;
import org.jowidgets.api.widgets.ITree;
import org.jowidgets.api.widgets.ITreeContainer;
import org.jowidgets.api.widgets.ITreeNode;
import org.jowidgets.api.widgets.ITreeNodeVisitor;
import org.jowidgets.api.widgets.descriptor.ITreeNodeDescriptor;
import org.jowidgets.common.types.Position;
import org.jowidgets.common.widgets.controller.IFocusListener;
import org.jowidgets.common.widgets.controller.IKeyEvent;
import org.jowidgets.common.widgets.controller.IKeyListener;
import org.jowidgets.common.widgets.controller.IPopupDetectionListener;
import org.jowidgets.common.widgets.controller.ITreeNodeListener;
import org.jowidgets.impl.base.delegate.DisposableDelegate;
import org.jowidgets.impl.base.delegate.PopupMenuCreationDelegate;
import org.jowidgets.impl.base.delegate.PopupMenuCreationDelegate.IPopupFactory;
import org.jowidgets.impl.base.delegate.TreeContainerDelegate;
import org.jowidgets.impl.event.TreePopupEvent;
import org.jowidgets.impl.widgets.common.wrapper.AbstractTreeNodeSpiWrapper;
import org.jowidgets.spi.widgets.ITreeNodeSpi;
import org.jowidgets.tools.controller.KeyObservable;
import org.jowidgets.tools.controller.KeyObservable.IKeyObservableCallback;
import org.jowidgets.tools.controller.PopupDetectionObservable;
import org.jowidgets.tools.controller.TreeNodeAdapter;
import org.jowidgets.tools.controller.TreeNodeCheckableObservable;
import org.jowidgets.tools.controller.TreeNodeObservable;
import org.jowidgets.util.Assert;
import org.jowidgets.util.EmptyCheck;
public class TreeNodeImpl extends AbstractTreeNodeSpiWrapper implements ITreeNode {
private final TreeImpl parentTree;
private final TreeNodeImpl parentNode;
private final TreeContainerDelegate treeContainerDelegate;
private final PopupMenuCreationDelegate popupMenuCreationDelegate;
private final IPopupDetectionListener popupListener;
private final DisposableDelegate disposableDelegate;
private final TreeNodeObservable treeNodeObservable;
private final TreeNodeCheckableObservable checkableObservable;
private final PopupDetectionObservable popupDetectionObservable;
private final KeyObservable keyObservable;
private final IKeyListener keyListener;
private final IPopupDetectionListener spiPopupDetectionListener;
private final ITreeNodeListener spiTreeNodeListener;
private final ITreeNodeListener autoCheckListener;
private final boolean autoCheckMode;
private final TreeAutoCheckPolicy autoCheckPolicy;
private IMenuModel popupMenuModel;
private IPopupMenu popupMenu;
private boolean onRemoveByDispose;
private boolean checkable;
public TreeNodeImpl(final TreeImpl parentTree, final TreeNodeImpl parentNode, final ITreeNodeSpi widget) {
this(parentTree, parentNode, widget, Toolkit.getBluePrintFactory().treeNode());
}
public TreeNodeImpl(
final TreeImpl parentTree,
final TreeNodeImpl parentNode,
final ITreeNodeSpi widget,
final ITreeNodeDescriptor descriptor) {
super(widget);
this.autoCheckMode = parentTree.getAutoCheckMode();
this.autoCheckPolicy = parentTree.getAutoCheckPolicy();
this.treeNodeObservable = new TreeNodeObservable();
this.checkableObservable = new TreeNodeCheckableObservable();
this.popupDetectionObservable = new PopupDetectionObservable();
this.checkable = true;
this.autoCheckListener = new TreeNodeAdapter() {
@Override
public void checkedChanged(final boolean checked) {
if (TreeAutoCheckPolicy.SINGLE_PATH.equals(autoCheckPolicy)) {
final TreeNodeImpl checkedNode = parentTree.getCheckedNode();
if (checkedNode != null) {
checkedNode.setChecked(false);
}
}
setAutoCheckChildState(checked);
final TreeNodeImpl parent = (TreeNodeImpl) getParent();
if (parent != null) {
parent.setAutoCheckParentState();
}
}
};
if (autoCheckMode) {
widget.addTreeNodeListener(autoCheckListener);
}
this.spiPopupDetectionListener = new IPopupDetectionListener() {
@Override
public void popupDetected(final Position position) {
popupDetectionObservable.firePopupDetected(position);
}
};
widget.addPopupDetectionListener(spiPopupDetectionListener);
this.spiTreeNodeListener = new ITreeNodeListener() {
@Override
public void selectionChanged(final boolean selected) {
treeNodeObservable.fireSelectionChanged(selected);
}
@Override
public void expandedChanged(final boolean expanded) {
treeNodeObservable.fireExpandedChanged(expanded);
}
@Override
public void checkedChanged(final boolean checked) {
treeNodeObservable.fireCheckedChanged(checked);
}
};
widget.addTreeNodeListener(spiTreeNodeListener);
this.popupListener = new IPopupDetectionListener() {
@Override
public void popupDetected(final Position position) {
if (popupMenuModel != null) {
if (popupMenu == null) {
popupMenu = popupMenuCreationDelegate.createPopupMenu();
}
if (popupMenu.getModel() != popupMenuModel) {
popupMenu.setModel(popupMenuModel);
}
popupMenu.show(position);
}
}
};
this.parentTree = parentTree;
this.parentNode = parentNode;
this.popupMenuCreationDelegate = new PopupMenuCreationDelegate(new IPopupFactory() {
@Override
public IPopupMenu create() {
return new PopupMenuImpl(getWidget().createPopupMenu(), parentTree);
}
});
this.disposableDelegate = new DisposableDelegate();
this.onRemoveByDispose = false;
if (descriptor.getForegroundColor() != null) {
setForegroundColor(descriptor.getForegroundColor());
}
if (descriptor.getBackgroundColor() != null) {
setBackgroundColor(descriptor.getBackgroundColor());
}
setExpanded(descriptor.isExpanded());
setSelected(descriptor.isSelected());
setText(descriptor.getText());
setToolTipText(descriptor.getToolTipText());
setIcon(descriptor.getIcon());
setMarkup(descriptor.getMarkup());
this.keyListener = new IKeyListener() {
@Override
public void keyReleased(final IKeyEvent event) {
if (parentTree.hasFocus() && isFirstSelected()) {
keyObservable.fireKeyReleased(event);
}
}
@Override
public void keyPressed(final IKeyEvent event) {
if (parentTree.hasFocus() && isFirstSelected()) {
keyObservable.fireKeyPressed(event);
}
}
};
this.keyObservable = new KeyObservable(new IKeyObservableCallback() {
@Override
public void onLastUnregistered() {
updateKeyListeners();
}
@Override
public void onFirstRegistered() {
updateKeyListeners();
}
});
addTreeNodeListener(new TreeNodeAdapter() {
@Override
public void expandedChanged(final boolean expanded) {
if (expanded) {
parentTree.getTreeObservable().fireNodeExpanded(TreeNodeImpl.this);
}
else {
parentTree.getTreeObservable().fireNodeCollapsed(TreeNodeImpl.this);
}
}
@Override
public void checkedChanged(final boolean checked) {
if (checked) {
parentTree.getTreeObservable().fireNodeChecked(TreeNodeImpl.this);
}
else {
parentTree.getTreeObservable().fireNodeUnchecked(TreeNodeImpl.this);
}
}
});
addPopupDetectionListener(new IPopupDetectionListener() {
@Override
public void popupDetected(final Position position) {
parentTree.getTreePopupDetectionObservable().firePopupDetected(new TreePopupEvent(position, TreeNodeImpl.this));
}
});
final IFocusListener focusListener = new IFocusListener() {
@Override
public void focusLost() {
updateKeyListeners();
}
@Override
public void focusGained() {
updateKeyListeners();
}
};
parentTree.addFocusListener(focusListener);
final ITreeSelectionListener treeSelectionListener = new ITreeSelectionListener() {
@Override
public void selectionChanged(final ITreeSelectionEvent event) {
updateKeyListeners();
}
};
parentTree.addTreeSelectionListener(treeSelectionListener);
addDisposeListener(new IDisposeListener() {
@Override
public void onDispose() {
parentTree.removeFocusListener(focusListener);
parentTree.removeTreeSelectionListener(treeSelectionListener);
parentTree.removeKeyListener(keyListener);
}
});
this.treeContainerDelegate = new TreeContainerDelegate(parentTree, parentNode, this, widget);
checkIcon();
}
private void updateKeyListeners() {
if (keyObservable.size() > 0 && parentTree.hasFocus() && isFirstSelected()) {
parentTree.addKeyListener(keyListener);
}
else {
parentTree.removeKeyListener(keyListener);
}
}
private void setAutoCheckChildState(final boolean checked) {
if (!autoCheckMode || !isCheckable()) {
return;
}
else {
getWidget().removeTreeNodeListener(autoCheckListener);
if (isGreyed() || isChecked() != checked) {
setChecked(checked);
}
int checkedCount = 0;
int greyedCount = 0;
for (final ITreeNode childNode : new LinkedList<ITreeNode>(getChildren())) {
((TreeNodeImpl) childNode).setAutoCheckChildState(checked);
if (childNode.isChecked()) {
checkedCount++;
}
else if (childNode.isGreyed()) {
greyedCount++;
}
if (checked && TreeAutoCheckPolicy.SINGLE_PATH.equals(autoCheckPolicy) && (greyedCount > 0 || checkedCount > 0)) {
break;
}
}
if (checked) {
if (checkedCount == getChildren().size()) {
setChecked(true);
if (TreeAutoCheckPolicy.SINGLE_PATH.equals(autoCheckPolicy)) {
parentTree.setCheckedNode(this);
}
}
else if (checkedCount > 0 || greyedCount > 0) {
setGreyed();
}
else {
setChecked(false);
}
}
getWidget().addTreeNodeListener(autoCheckListener);
}
}
private void setAutoCheckParentState() {
if (!autoCheckMode) {
return;
}
else {
getWidget().removeTreeNodeListener(autoCheckListener);
int checkedCount = 0;
int greyedCount = 0;
for (final ITreeNode childNode : new LinkedList<ITreeNode>(getChildren())) {
if (childNode.isChecked()) {
checkedCount++;
}
else if (childNode.isGreyed()) {
greyedCount++;
}
}
if (checkedCount == getChildren().size()) {
setChecked(true);
}
else if (checkedCount > 0 || greyedCount > 0) {
setGreyed();
}
else {
setChecked(false);
}
final TreeNodeImpl parent = (TreeNodeImpl) getParent();
if (parent != null) {
parent.setAutoCheckParentState();
}
getWidget().addTreeNodeListener(autoCheckListener);
}
}
@Override
public void setGreyed() {
if (!isGreyed()) {
super.setGreyed(true);
treeNodeObservable.fireCheckedChanged(false);
}
}
@Override
public CheckedState getCheckedState() {
if (isChecked()) {
return CheckedState.CHECKED;
}
else if (isGreyed()) {
return CheckedState.GREYED;
}
else {
return CheckedState.UNCHECKED;
}
}
@Override
public void setCheckedState(final CheckedState state) {
Assert.paramNotNull(state, "state");
if (CheckedState.CHECKED.equals(state)) {
setChecked(true);
}
else if (CheckedState.GREYED.equals(state)) {
setGreyed(true);
}
else if (CheckedState.UNCHECKED.equals(state)) {
setChecked(false);
}
else {
throw new IllegalArgumentException("The state '" + state + "' is not known");
}
}
@Override
public void setCheckable(final boolean checkable) {
if (autoCheckMode && checkable && getParent() != null && !getParent().isCheckable()) {
return;
}
if (this.checkable != checkable) {
getWidget().setCheckable(checkable);
this.checkable = checkable;
checkableObservable.fireCheckableChanged(checkable);
if (autoCheckMode) {
for (final ITreeNode child : new LinkedList<ITreeNode>(getChildren())) {
child.setCheckable(checkable);
}
}
}
}
@Override
public boolean isCheckable() {
return checkable;
}
@Override
public boolean isUnchecked() {
return !isGreyed() && !isChecked();
}
@Override
public void addTreeNodeListener(final ITreeNodeListener listener) {
treeNodeObservable.addTreeNodeListener(listener);
}
@Override
public void addCheckableListener(final ITreeNodeCheckableListener listener) {
checkableObservable.addCheckableListener(listener);
}
@Override
public void removeCheckableListener(final ITreeNodeCheckableListener listener) {
checkableObservable.removeCheckableListener(listener);
}
@Override
public void addPopupDetectionListener(final IPopupDetectionListener listener) {
popupDetectionObservable.addPopupDetectionListener(popupListener);
}
@Override
public void removeTreeNodeListener(final ITreeNodeListener listener) {
treeNodeObservable.removeTreeNodeListener(listener);
}
@Override
public void removePopupDetectionListener(final IPopupDetectionListener listener) {
popupDetectionObservable.removePopupDetectionListener(listener);
}
@Override
public void addKeyListener(final IKeyListener listener) {
keyObservable.addKeyListener(listener);
}
@Override
public void removeKeyListener(final IKeyListener listener) {
keyObservable.removeKeyListener(listener);
}
@Override
public ITreeNode getParent() {
return parentNode;
}
@Override
public ITree getTree() {
return parentTree;
}
@Override
public List<ITreeNode> getPath() {
final LinkedList<ITreeNode> result = new LinkedList<ITreeNode>();
ITreeNode node = this;
while (node != null) {
result.push(node);
node = node.getParent();
}
return result;
}
@Override
public void dispose() {
if (!isDisposed()) {
if (parentNode != null && parentNode.getChildren().contains(this) && !onRemoveByDispose) {
onRemoveByDispose = true;
parentNode.removeNode(this); //this will invoke dispose by the parent node
onRemoveByDispose = false;
}
else {
popupMenuCreationDelegate.dispose();
treeContainerDelegate.dispose();
disposableDelegate.dispose();
treeNodeObservable.dispose();
popupDetectionObservable.dispose();
getWidget().removePopupDetectionListener(spiPopupDetectionListener);
getWidget().removeTreeNodeListener(spiTreeNodeListener);
}
}
}
@Override
public boolean isDisposed() {
return disposableDelegate.isDisposed();
}
@Override
public void addDisposeListener(final IDisposeListener listener) {
disposableDelegate.addDisposeListener(listener);
}
@Override
public void removeDisposeListener(final IDisposeListener listener) {
disposableDelegate.removeDisposeListener(listener);
}
@Override
public IPopupMenu createPopupMenu() {
return popupMenuCreationDelegate.createPopupMenu();
}
@Override
public ITreeContainer getParentContainer() {
return treeContainerDelegate.getParentContainer();
}
@Override
public ITreeNode addNode() {
final ITreeNode result = treeContainerDelegate.addNode();
afterNodeAdded(result);
return result;
}
@Override
public ITreeNode addNode(final int index) {
final ITreeNode result = treeContainerDelegate.addNode(index);
afterNodeAdded(result);
return result;
}
@Override
public ITreeNode addNode(final ITreeNodeDescriptor descriptor) {
final ITreeNode result = treeContainerDelegate.addNode(descriptor);
afterNodeAdded(result);
return result;
}
@Override
public ITreeNode addNode(final int index, final ITreeNodeDescriptor descriptor) {
final ITreeNode result = treeContainerDelegate.addNode(index, descriptor);
afterNodeAdded(result);
return result;
}
@Override
public void removeNode(final ITreeNode node) {
treeContainerDelegate.removeNode(node);
}
@Override
public void removeNode(final int index) {
treeContainerDelegate.removeNode(index);
}
@Override
public void removeAllNodes() {
treeContainerDelegate.removeAllNodes();
}
@Override
public List<ITreeNode> getChildren() {
return treeContainerDelegate.getChildren();
}
@Override
public boolean accept(final ITreeNodeVisitor visitor) {
Assert.paramNotNull(visitor, "visitor");
if (visitor.visitEnter(this)) {
treeContainerDelegate.accept(visitor);
}
return visitor.visitLeave(this);
}
@Override
public void setAllChildrenExpanded(final boolean expanded) {
setAllChildrenExpanded(null, expanded);
}
@Override
public void setAllChildrenExpanded(final Integer pivotLevel, final boolean expanded) {
if (pivotLevel == null) {
setExpanded(expanded);
treeContainerDelegate.setAllChildrenExpanded(expanded);
}
else {
final int pivot = pivotLevel.intValue();
Assert.paramNotNegative(pivot, "pivot");
int newPivot = pivot;
if (newPivot > 0) {
newPivot--;
}
if (expanded) {
setExpanded(true);
if (pivot > 0) {
treeContainerDelegate.setAllChildrenExpanded(Integer.valueOf(newPivot), expanded);
}
}
else {//expanded == false here
if (pivot == 0) {//do not collapse nodes above the pivot level
setExpanded(false);
}
treeContainerDelegate.setAllChildrenExpanded(Integer.valueOf(newPivot), expanded);
}
}
}
@Override
public void setAllChildrenBelowExpandedAboveCollapsed(final int pivot) {
Assert.paramNotNegative(pivot, "pivot");
int newPivot = pivot;
if (newPivot > 0) {
newPivot--;
}
if (pivot > 0) {
setExpanded(pivot > 0);
}
else {
setExpanded(false);
}
treeContainerDelegate.setAllChildrenBelowExpandedAboveCollapsed(Integer.valueOf(newPivot));
}
@Override
public void setAllChildrenChecked(final boolean checked) {
treeContainerDelegate.setAllChildrenChecked(checked);
}
@Override
public int getLevel() {
return treeContainerDelegate.getLevel();
}
@Override
public boolean isLeaf() {
return getChildren().size() == 0;
}
@Override
public boolean isTopLevel() {
return getParent() == null;
}
@Override
public void setPopupMenu(final IMenuModel popupMenuModel) {
if (popupMenuModel == null && this.popupMenuModel != null) {
removePopupDetectionListener(popupListener);
}
else if (popupMenuModel != null && this.popupMenuModel == null) {
addPopupDetectionListener(popupListener);
}
this.popupMenuModel = popupMenuModel;
}
private boolean isFirstSelected() {
final Collection<ITreeNode> selection = parentTree.getSelection();
if (selection.size() > 0) {
return selection.iterator().next() == this;
}
else {
return false;
}
}
private void afterNodeAdded(final ITreeNode node) {
checkIcon();
setAutoCheckParentState();
if (!isCheckable()) {
node.setCheckable(false);
}
}
private void checkIcon() {
if (getIcon() == null) {
if (isLeaf()) {
getWidget().setIcon(parentTree.getDefaultLeafIcon());
}
else {
getWidget().setIcon(parentTree.getDefaultInnerIcon());
}
}
}
@Override
public String toString() {
final String text = getText();
if (!EmptyCheck.isEmpty(text)) {
return text;
}
else {
return super.toString();
}
}
}