/*
* 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.nodeEditor;
import jetbrains.mps.editor.runtime.descriptor.EditorBuilderEnvironment;
import jetbrains.mps.editor.runtime.style.StyleAttributes;
import jetbrains.mps.editor.runtime.style.StyleImpl;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.AttributeOperations;
import jetbrains.mps.nodeEditor.cells.EditorCellFactoryImpl;
import jetbrains.mps.nodeEditor.cells.EditorCell_Basic;
import jetbrains.mps.nodeEditor.cells.EditorCell_Collection;
import jetbrains.mps.nodeEditor.cells.EditorCell_Constant;
import jetbrains.mps.nodeEditor.cells.EditorCell_Property;
import jetbrains.mps.nodeEditor.cells.ModelAccessor;
import jetbrains.mps.openapi.editor.EditorContext;
import jetbrains.mps.openapi.editor.cells.EditorCell;
import jetbrains.mps.openapi.editor.cells.EditorCellFactory;
import jetbrains.mps.openapi.editor.style.Style;
import jetbrains.mps.openapi.editor.style.StyleAttribute;
import jetbrains.mps.openapi.editor.update.UpdateSession;
import jetbrains.mps.openapi.editor.update.AttributeKind;
import jetbrains.mps.smodel.SNodeUtil;
import jetbrains.mps.util.EqualUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.language.SConcept;
import org.jetbrains.mps.openapi.language.SContainmentLink;
import org.jetbrains.mps.openapi.language.SDataType;
import org.jetbrains.mps.openapi.language.SPrimitiveDataType;
import org.jetbrains.mps.openapi.language.SProperty;
import org.jetbrains.mps.openapi.language.SReferenceLink;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SReference;
import java.awt.Color;
import java.util.Collection;
import java.util.Deque;
import java.util.LinkedHashSet;
import java.util.LinkedList;
public abstract class AbstractDefaultEditor extends DefaultNodeEditor implements EditorBuilderEnvironment {
private static final String NAME_NAME = "name";
private static final int NAME_PRIORITY = 10000;
private static final String IDENTIFIER_NAME = "identifier";
private static final int IDENTIFIER_PRIORITY = 1700;
private static final int NAME_ADD_PRIORITY = 1000;
private static final String QUALIFIED_NAME = "qualified";
private static final int QUALIFIED_PRIORITY = 200;
private static final Color FIRST_LABEL_BACKGROUND_COLOR = new Color(107, 142, 20, 100);
private SNode mySNode;
private SConcept myConcept;
private EditorContext myEditorContext;
private Deque<EditorCell_Collection> collectionStack = new LinkedList<EditorCell_Collection>();
private int currentCollectionIdNumber = 0;
private int currentConstantIdNumber = 0;
private Collection<SProperty> myProperties = new LinkedHashSet<SProperty>();
private Collection<SReferenceLink> myReferenceLinks = new LinkedHashSet<SReferenceLink>();
private Collection<SContainmentLink> myContainmentLinks = new LinkedHashSet<SContainmentLink>();
public AbstractDefaultEditor(@NotNull SConcept concept) {
myConcept = concept;
}
@Override
public EditorCell createEditorCell(EditorContext editorContext, SNode node) {
myEditorContext = editorContext;
mySNode = node;
assert myConcept.equals(mySNode.getConcept());
init();
SProperty nameProperty = getNameProperty();
EditorCell_Collection mainCellCollection = pushCollection();
mainCellCollection.setBig(true);
mainCellCollection.setCellContext(getCellFactory().getCellContext());
addLabel(camelToLabel(myConcept.getName()));
addStyle(StyleAttributes.TEXT_BACKGROUND_COLOR, FIRST_LABEL_BACKGROUND_COLOR);
if (nameProperty != null) {
getProperties().remove(nameProperty);
addPropertyCell(nameProperty);
}
addReferences();
if (!getContainmentLinks().isEmpty() || !getProperties().isEmpty() || isAttribute()) {
addPropertiesAndChildren();
}
popCollection();
return mainCellCollection;
}
protected void init() {
assert mySNode != null && myConcept != null;
for (SProperty sProperty : mySNode.getProperties()) {
if (!sProperty.getOwner().equals(SNodeUtil.concept_BaseConcept)) {
myProperties.add(sProperty);
}
}
for (SReference sReference : mySNode.getReferences()) {
SReferenceLink link = sReference.getLink();
assert link != null : "Null meta-link from node: " + this.mySNode + ", role: " + sReference.getRole();
if (!link.getOwner().equals(SNodeUtil.concept_BaseConcept)) {
myReferenceLinks.add(link);
}
}
for (SNode child : this.mySNode.getChildren()) {
SContainmentLink containmentLink = child.getContainmentLink();
assert containmentLink != null : "Null meta-containmentLink returned for the child of node: " + this.mySNode + ", child: " + child;
if (!containmentLink.getOwner().equals(SNodeUtil.concept_BaseConcept)) {
myContainmentLinks.add(containmentLink);
}
}
}
protected void addProperty(SProperty property) {
myProperties.add(property);
}
protected void addContainmentLink(SContainmentLink containmentLink) {
myContainmentLinks.add(containmentLink);
}
protected void addReferenceLink(SReferenceLink link) {
myReferenceLinks.add(link);
}
private SProperty getNameProperty() {
SProperty nameProperty = null;
int maxPriority = 0;
for (SProperty property : getProperties()) {
SDataType dataType = property.getType();
if (!(dataType instanceof SPrimitiveDataType && ((SPrimitiveDataType) dataType).getType() == SPrimitiveDataType.STRING)) {
continue;
}
String propertyName = property.getName();
int propertyPriority = getPropertyPriority(propertyName);
if (maxPriority < propertyPriority) {
maxPriority = propertyPriority;
nameProperty = property;
}
}
return nameProperty;
}
private int getPropertyPriority(@NotNull String propertyName) {
if (NAME_NAME.equals(propertyName)) {
return NAME_PRIORITY;
}
int priority = 0;
if (propertyName.toLowerCase().contains(IDENTIFIER_NAME)) {
priority += IDENTIFIER_PRIORITY;
}
if (propertyName.toLowerCase().contains(NAME_NAME)) {
priority += NAME_ADD_PRIORITY;
}
if (propertyName.toLowerCase().contains(QUALIFIED_NAME)) {
priority += QUALIFIED_PRIORITY;
}
return priority;
}
private void addPropertiesAndChildren() {
addLabel("{");
addStyle(StyleAttributes.MATCHING_LABEL, "body-brace");
addNewLine();
pushCollection();
setIndent(collectionStack.peek());
addProperties();
addLabel("");
addNewLine();
getCellFactory().pushCellContext();
getCellFactory().removeCellContextHints(EditorCellFactoryImpl.BASE_REFLECTIVE_EDITOR_HINT);
try {
addChildren();
} finally {
getCellFactory().popCellContext();
}
addAttributedEntity();
popCollection();
addLabel("}");
addStyle(StyleAttributes.MATCHING_LABEL, "body-brace");
}
protected Collection<SProperty> getProperties() {
return myProperties;
}
protected Collection<SReferenceLink> getReferenceLinks() {
return myReferenceLinks;
}
protected Collection<SContainmentLink> getContainmentLinks() {
return myContainmentLinks;
}
protected abstract void addPropertyCell(final SProperty property);
protected abstract void addChildCell(final SContainmentLink referenceLink);
protected abstract void addReferenceCell(final SReferenceLink referenceLink);
private void addProperties() {
for (SProperty property : getProperties()) {
addRoleLabel(property.getName(), "property");
addPropertyCell(property);
addNewLine();
}
}
private void addReferences() {
for (SReferenceLink reference : getReferenceLinks()) {
addRoleLabel(reference.getName(), "reference");
addReferenceCell(reference);
}
}
private void addChildren() {
for (SContainmentLink link : getContainmentLinks()) {
addRoleLabel(link.getName(), "link");
addNewLine();
addChildCell(link);
addNewLine();
}
}
private void addAttributedEntity() {
if (AttributeOperations.isNodeAttribute(mySNode)) {
addAttributedCell("attributed node:", AttributeKind.NODE);
} else if (AttributeOperations.isPropertyAttribute(mySNode)) {
addAttributedCell("attributed property:", AttributeKind.PROPERTY);
} else if (AttributeOperations.isLinkAttribute(mySNode)) {
addAttributedCell("attributed reference:", AttributeKind.REFERENCE);
}
}
private void addAttributedCell(String label, AttributeKind attributeKind) {
addLabel(label);
addNewLine();
EditorCell editorCell = myEditorContext.getEditorComponent().getUpdater().getCurrentUpdateSession().getAttributedCell(attributeKind, mySNode);
addCell(editorCell);
addNewLine();
}
private boolean isAttribute() {
return AttributeOperations.isNodeAttribute(mySNode) || AttributeOperations.isPropertyAttribute(mySNode) || AttributeOperations.isLinkAttribute(mySNode);
}
private void addRoleLabel(String role, String type) {
if (role == null) {
role = "<no " + type + ">";
}
addLabel(camelToLabel(role));
addLabel(":");
}
protected void addLabel(String label) {
addLabel(label, false);
}
protected void addLabel(String label, boolean editable) {
EditorCell_Collection cellCollection = collectionStack.peek();
EditorCell_Constant childLabel = new EditorCell_Constant(getEditorContext(), mySNode, label, editable);
childLabel.setCellId("constant_" + currentConstantIdNumber);
cellCollection.addEditorCell(childLabel);
currentConstantIdNumber++;
}
protected void addCell(EditorCell cell) {
collectionStack.peek().addEditorCell(cell);
}
private String camelToLabel(String text) {
StringBuilder sb = new StringBuilder();
char[] cs = text.toCharArray();
for (int i = 0; i < cs.length; i++) {
if (Character.isUpperCase(cs[i])) {
if (sb.length() > 0) {
sb.append(' ');
}
if (i + 1 < cs.length && Character.isLowerCase(cs[i + 1])) {
sb.append(Character.toLowerCase(cs[i]));
continue;
}
while (i + 1 < cs.length && !(Character.isLowerCase(cs[i + 1]))) {
sb.append(cs[i]);
i++;
}
if (i + 1 < cs.length) {
i--;
continue;
}
}
sb.append(cs[i]);
}
return sb.toString();
}
protected EditorCell createReferentEditorCell(EditorContext editorContext, SReferenceLink link, final SNode targetNode) {
EditorCell_Property result = new EditorCell_Property(editorContext, new ModelAccessor() {
public String getText() {
String name = targetNode.getName();
if (name != null) {
return name;
}
return targetNode.getPresentation();
}
public void setText(String s) {
}
public boolean isValidText(String s) {
return EqualUtil.equals(s, getText());
}
}, targetNode);
result.setRole(link.getName());
result.setReferenceCell(true);
return result;
}
protected void setSemanticNodeToCells(jetbrains.mps.openapi.editor.cells.EditorCell rootCell, SNode semanticNode) {
if (!(rootCell instanceof EditorCell_Basic) || semanticNode == null) {
return;
}
((EditorCell_Basic) rootCell).setSNode(semanticNode);
if (rootCell instanceof jetbrains.mps.openapi.editor.cells.EditorCell_Collection) {
for (EditorCell child : ((jetbrains.mps.openapi.editor.cells.EditorCell_Collection) rootCell)) {
setSemanticNodeToCells(child, semanticNode);
}
}
}
private EditorCell_Collection pushCollection() {
EditorCell_Collection newCollection = EditorCell_Collection.createIndent2(getEditorContext(), mySNode);
collectionStack.push(newCollection);
return newCollection;
}
private EditorCell_Collection popCollection() {
if (collectionStack.isEmpty()) {
return null;
}
EditorCell_Collection result = collectionStack.pop();
result.setCellId("collection_" + currentCollectionIdNumber);
currentCollectionIdNumber++;
if (!collectionStack.isEmpty()) {
collectionStack.peek().addEditorCell(result);
}
return result;
}
protected void addNewLine() {
addStyle(getLastCell(), StyleAttributes.INDENT_LAYOUT_NEW_LINE);
}
private EditorCell getLastCell() {
EditorCell_Collection collection = collectionStack.peek();
EditorCell lastCell = collection;
if (!collection.isEmpty()) {
lastCell = collection.lastCell();
}
return lastCell;
}
protected void setIndent(EditorCell cell) {
addStyle(cell, StyleAttributes.INDENT_LAYOUT_INDENT);
}
protected <T> void addStyle(StyleAttribute<T> attribute, T value) {
addStyle(getLastCell(), attribute, value);
}
protected <T> void addStyle(EditorCell cell, StyleAttribute<T> attribute, T value) {
Style style = new StyleImpl();
style.set(attribute, value);
cell.getStyle().putAll(style);
}
protected void addStyle(EditorCell cell, StyleAttribute<Boolean> attribute) {
addStyle(cell, attribute, true);
}
public static AbstractDefaultEditor createEditor(SNode node) {
SConcept concept = node.getConcept();
return concept.isValid() ? new DefaultEditor(concept) : new ReadOnlyDefaultEditor(concept);
}
/**
* @deprecated since MPS 3.5 use {{@link #getNode()}
*/
@Deprecated
protected SNode getSNode() {
return mySNode;
}
protected SConcept getConcept() {
return myConcept;
}
public EditorContext getEditorContext() {
return myEditorContext;
}
@Override
public SNode getNode() {
return mySNode;
}
@Override
public EditorCellFactory getCellFactory() {
return getUpdateSession().getCellFactory();
}
@Override
public UpdateSession getUpdateSession() {
return getEditorContext().getEditorComponent().getUpdater().getCurrentUpdateSession();
}
}