/*
* Copyright 2003-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jetbrains.mps.ide.ui.dialogs.properties.genpriorities;
import com.intellij.ui.CheckboxTree;
import com.intellij.ui.CheckboxTree.CheckboxTreeCellRenderer;
import com.intellij.ui.CheckboxTreeBase.CheckPolicy;
import com.intellij.ui.CheckedTreeNode;
import com.intellij.ui.ColoredSideBorder;
import com.intellij.ui.ColoredTreeCellRenderer;
import com.intellij.ui.JBColor;
import com.intellij.ui.LayeredIcon;
import jetbrains.mps.generator.impl.RuleUtil;
import jetbrains.mps.icons.MPSIcons.Nodes;
import jetbrains.mps.icons.MPSIcons.Nodes.Models;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SModelOperations;
import jetbrains.mps.project.structure.modules.mappingpriorities.MappingConfig_AbstractRef;
import jetbrains.mps.project.structure.modules.mappingpriorities.MappingConfig_ExternalRef;
import jetbrains.mps.project.structure.modules.mappingpriorities.MappingConfig_RefAllGlobal;
import jetbrains.mps.project.structure.modules.mappingpriorities.MappingConfig_RefAllLocal;
import jetbrains.mps.project.structure.modules.mappingpriorities.MappingConfig_RefSet;
import jetbrains.mps.project.structure.modules.mappingpriorities.MappingConfig_SimpleRef;
import jetbrains.mps.smodel.Generator;
import jetbrains.mps.smodel.ModelAccess;
import jetbrains.mps.smodel.SNodePointer;
import jetbrains.mps.util.Computable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.model.SModelReference;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeReference;
import org.jetbrains.mps.openapi.module.SModule;
import org.jetbrains.mps.openapi.module.SModuleReference;
import org.jetbrains.mps.openapi.module.SRepository;
import org.jetbrains.mps.openapi.persistence.PersistenceFacade;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JTree;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
public class GeneratorPrioritiesTree {
private CheckedTreeNodeEx myRootNode;
private CheckboxTree myCheckboxTree;
// FIXME isRight = !isLeft, depGenerators used only when isRight - bloody sh!t. Why not initTree is package local, so that caller could configure this Tree(?!) as needed?
public GeneratorPrioritiesTree(@NotNull final SRepository repo, @NotNull final Generator generator, @NotNull MappingConfig_AbstractRef mapping, boolean isLeft, final Set<SModuleReference> depGenerators) {
myRootNode = new CheckedTreeNodeEx(null, "Generators", createRootIcon());
final boolean isRight = !isLeft;
repo.getModelAccess().runReadAction(new Runnable() {
@Override
public void run() {
initTree(generator);
if (isRight) {
for (SModuleReference ref : depGenerators) {
SModule gen = ref.resolve(repo);
if (gen instanceof Generator)
initTree((Generator) gen);
}
}
}
});
setCheckedStateForRoot(myRootNode, mapping);
myCheckboxTree = new CheckboxTree(getCheckboxTreeCellRenderer(), myRootNode, new CheckPolicy(true, true, false, true));
myCheckboxTree.addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent e) { if(myCheckboxTree.getSelectionCount() == 0) myCheckboxTree.setSelectionRow(0); }
@Override
public void focusLost(FocusEvent e) {}
});
myCheckboxTree.setRootVisible(isRight);
expandAllRows(myCheckboxTree);
checkChildren(myCheckboxTree);
setCheckedUnder(myRootNode);
}
private void initTree(Generator generator) {
CheckedTreeNodeEx generatorNode = new CheckedTreeNodeEx(generator.getModuleReference(), generator.getAlias(), Nodes.Generator);
myRootNode.add(generatorNode);
for (SModel templateModel : generator.getOwnTemplateModels()) {
String modelName = templateModel.getName().getSimpleName();
CheckedTreeNodeEx templateNode = new CheckedTreeNodeEx(templateModel.getReference(), modelName, Models.TemplatesModel);
generatorNode.add(templateNode);
for (SNode mapping : SModelOperations.roots(templateModel, RuleUtil.concept_MappingConfiguration)) {
CheckedTreeNodeEx mappingNode = new CheckedTreeNodeEx(mapping.getReference(), mapping.getName(), Nodes.MappingConfig);
templateNode.add(mappingNode);
}
}
}
private void setCheckedStateForRoot(CheckedTreeNodeEx node, MappingConfig_AbstractRef mapping) {
if (mapping instanceof MappingConfig_RefAllGlobal) {
node.setChecked(true);
} else if (mapping instanceof MappingConfig_ExternalRef) {
setCheckedStateForRef(node, (MappingConfig_ExternalRef) mapping);
} else if (mapping instanceof MappingConfig_RefSet) {
for (MappingConfig_AbstractRef ref : ((MappingConfig_RefSet) mapping).getMappingConfigs()) {
setCheckedStateForRef(node, (MappingConfig_ExternalRef) ref);
}
}
}
private void setCheckedStateForRef(CheckedTreeNodeEx node, MappingConfig_ExternalRef mapping) {
SModuleReference modRef = mapping.getGenerator();
Enumeration children = node.children();
while (children.hasMoreElements()) {
CheckedTreeNodeEx child = (CheckedTreeNodeEx) children.nextElement();
if (child.getUserObject().equals(modRef)) {
MappingConfig_AbstractRef innerOperand = mapping.getMappingConfig();
if (innerOperand instanceof MappingConfig_RefAllLocal) {
child.setChecked(true);
} else if (innerOperand instanceof MappingConfig_SimpleRef) {
setCheckedStateForModel(child, (MappingConfig_SimpleRef) innerOperand);
} else if (innerOperand instanceof MappingConfig_RefSet) {
for (MappingConfig_AbstractRef ref : ((MappingConfig_RefSet) innerOperand).getMappingConfigs()) {
if (ref instanceof MappingConfig_SimpleRef) {
setCheckedStateForModel(child, (MappingConfig_SimpleRef) ref);
} else if (ref instanceof MappingConfig_RefSet) {
setCheckedStateForSet(child, (MappingConfig_RefSet) ref);
} else if (ref instanceof MappingConfig_ExternalRef) {
setCheckedStateForRef((CheckedTreeNodeEx) child.getParent(), (MappingConfig_ExternalRef) ref);
}
}
}
}
}
}
private void setCheckedStateForSet(CheckedTreeNodeEx node, MappingConfig_RefSet mapping) {
for (MappingConfig_AbstractRef ref : mapping.getMappingConfigs()) {
if (ref instanceof MappingConfig_SimpleRef) {
setCheckedStateForModel(node, (MappingConfig_SimpleRef) ref);
} else if (ref instanceof MappingConfig_RefSet) {
setCheckedStateForSet(node, (MappingConfig_RefSet) ref);
}
}
}
private void setCheckedStateForModel(CheckedTreeNodeEx node, MappingConfig_SimpleRef mapping) {
SModelReference modRef = PersistenceFacade.getInstance().createModelReference(mapping.getModelUID());
Enumeration children = node.children();
while (children.hasMoreElements()) {
CheckedTreeNodeEx child = (CheckedTreeNodeEx) children.nextElement();
if (child.getUserObject().equals(modRef)) {
if (mapping.includesAll()) {
child.setChecked(true);
} else {
setCheckedStateForNode(child, mapping);
}
}
}
}
private void setCheckedStateForNode(CheckedTreeNodeEx node, MappingConfig_SimpleRef mapping) {
SNodeReference nodeRef = new SNodePointer(mapping.getModelUID(), mapping.getNodeID());
Enumeration children = node.children();
while (children.hasMoreElements()) {
CheckedTreeNodeEx child = (CheckedTreeNodeEx) children.nextElement();
if (child.getUserObject().equals(nodeRef)) {
child.setChecked(true);
}
}
}
public MappingConfig_AbstractRef getEditResult() {
setCheckedUnder(myRootNode);
return ModelAccess.instance().runReadAction(new Computable<MappingConfig_AbstractRef>() {
public MappingConfig_AbstractRef compute() {
return getRootMapping();
}
});
}
private MappingConfig_AbstractRef getRootMapping() {
if (!(myRootNode.isChecksUnder())) {
return new MappingConfig_AbstractRef();
}
if (myRootNode.isChecked()) {
return new MappingConfig_RefAllGlobal();
}
List<CheckedTreeNodeEx> chChildren = myRootNode.getChildrenWithChecks();
if (chChildren.size() == 1) {
return getGeneratorMappingRef(chChildren.get(0));
} else {
MappingConfig_RefSet result = new MappingConfig_RefSet();
for (CheckedTreeNodeEx child : chChildren) {
result.getMappingConfigs().add(getGeneratorMappingRef(child));
}
return result;
}
}
private MappingConfig_AbstractRef getGeneratorMappingRef(CheckedTreeNodeEx generatorNode) {
MappingConfig_ExternalRef result = new MappingConfig_ExternalRef();
assert generatorNode.getUserObject() instanceof SModuleReference;
result.setGenerator((SModuleReference) generatorNode.getUserObject());
if (generatorNode.isChecked()) {
result.setMappingConfig(new MappingConfig_RefAllLocal());
} else {
List<CheckedTreeNodeEx> chChildren = generatorNode.getChildrenWithChecks();
if (chChildren.size() == 1) {
result.setMappingConfig(getModelMappingRef(chChildren.get(0)));
} else {
MappingConfig_RefSet modelsResult = new MappingConfig_RefSet();
for (CheckedTreeNodeEx child : chChildren) {
modelsResult.getMappingConfigs().add(getModelMappingRef(child));
}
result.setMappingConfig(modelsResult);
}
}
return result;
}
private MappingConfig_AbstractRef getModelMappingRef(CheckedTreeNodeEx templateModelNode) {
assert templateModelNode.getUserObject() instanceof SModelReference;
if (!(templateModelNode.isChecksUnder())) {
return new MappingConfig_AbstractRef();
}
if (templateModelNode.isChecked()) {
MappingConfig_SimpleRef result = new MappingConfig_SimpleRef();
result.setModelUID(templateModelNode.getUserObject().toString());
result.setNodeID("*");
return result;
}
List<CheckedTreeNodeEx> chChildren = templateModelNode.getChildrenWithChecks();
if (chChildren.size() == 1) {
return getNodeMappingRef(chChildren.get(0));
} else {
MappingConfig_RefSet result = new MappingConfig_RefSet();
for (CheckedTreeNodeEx child : chChildren) {
result.getMappingConfigs().add(getNodeMappingRef(child));
}
return result;
}
}
private MappingConfig_AbstractRef getNodeMappingRef(CheckedTreeNodeEx mapConfigNode) {
assert mapConfigNode.getUserObject() instanceof SNodeReference;
SNodeReference mapConfigRef = (SNodeReference) mapConfigNode.getUserObject();
MappingConfig_SimpleRef result = new MappingConfig_SimpleRef();
result.setModelUID(mapConfigRef.getModelReference().toString());
result.setNodeID(mapConfigRef.getNodeId().toString());
return result;
}
private static boolean setCheckedUnder(CheckedTreeNodeEx root) {
boolean childChecks = false;
Enumeration<CheckedTreeNodeEx> children = root.children();
while (children.hasMoreElements()) {
CheckedTreeNodeEx child = children.nextElement();
childChecks = childChecks | setCheckedUnder(child);
}
boolean checksUnder = root.isChecked() || childChecks;
root.setChecksUnder(checksUnder);
return checksUnder;
}
public CheckboxTree getTree() {
return myCheckboxTree;
}
public JComponent getTreePanel() {
return myCheckboxTree;
}
public static CheckboxTreeCellRenderer getCheckboxTreeCellRenderer() {
return getCheckboxTreeCellRenderer(true);
}
public static CheckboxTreeCellRenderer getCheckboxTreeCellRenderer(final boolean withCheckboxes) {
return new CheckboxTreeCellRenderer() {
@Override
public void customizeRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
super.customizeRenderer(tree, value, selected, expanded, leaf, row, hasFocus);
ColoredTreeCellRenderer renderer = getTextRenderer();
JCheckBox checkBox = getCheckbox();
if (value instanceof CheckedTreeNodeEx) {
CheckedTreeNodeEx checkedTreeNode = (CheckedTreeNodeEx) value;
renderer.append(checkedTreeNode.getPresentation());
renderer.setIcon(checkedTreeNode.getIcon());
checkBox.setSelected(checkedTreeNode.isChecked());
checkBox.setVisible(withCheckboxes);
if(checkedTreeNode.equals(tree.getModel().getRoot())) {
if(!GeneratorPrioritiesTree.setCheckedUnder(checkedTreeNode))
tree.setBorder(new ColoredSideBorder(JBColor.RED, JBColor.RED, JBColor.RED, JBColor.RED, 1));
else
tree.setBorder(BorderFactory.createEmptyBorder());
}
}
}
};
}
public static final void expandAllRows(JTree tree) {
for (int i = 0; i < tree.getRowCount(); i++) {
tree.expandRow(i);
}
}
private static void checkChildren(CheckboxTree checkboxTree) {
if(checkboxTree.getModel().getRoot() instanceof CheckedTreeNodeEx) {
Queue<CheckedTreeNodeEx> treeNodes = new LinkedList<CheckedTreeNodeEx>();
treeNodes.add((CheckedTreeNodeEx) checkboxTree.getModel().getRoot());
while (!treeNodes.isEmpty()) {
CheckedTreeNodeEx treeNode = treeNodes.poll();
treeNodes.addAll(Collections.list(treeNode.children()));
if(treeNode.getParent() instanceof CheckedTreeNodeEx && ((CheckedTreeNodeEx)treeNode.getParent()).isChecked()) {
treeNode.setChecked(true);
}
}
}
}
private static Icon createRootIcon() {
LayeredIcon layeredIcon = new LayeredIcon(3);
layeredIcon.setIcon(Nodes.Generator, 0, +2, -2);
layeredIcon.setIcon(Nodes.Generator, 1, 0, 0);
layeredIcon.setIcon(Nodes.Generator, 2, -2, +2);
return layeredIcon;
}
private static class CheckedTreeNodeEx extends CheckedTreeNode {
private boolean myChecksUnder;
private String myText;
private Icon myIcon;
public CheckedTreeNodeEx(Object value) {
super(value);
setChecked(false);
myChecksUnder = false;
}
public CheckedTreeNodeEx(Object value, String text, Icon icon) {
this(value);
myText = text;
myIcon = icon;
}
public boolean isChecksUnder() {
return myChecksUnder;
}
public void setChecksUnder(boolean checksUnder) {
myChecksUnder = checksUnder;
}
public String getPresentation() {
return myText;
}
public Icon getIcon() {
return myIcon;
}
/*package*/ List<CheckedTreeNodeEx> getChildrenWithChecks() {
List<CheckedTreeNodeEx> result = new ArrayList<CheckedTreeNodeEx>();
Enumeration children = children();
while (children.hasMoreElements()) {
CheckedTreeNodeEx child = (CheckedTreeNodeEx) children.nextElement();
if (child.isChecksUnder()) {
result.add(child);
}
}
return result;
}
}
}