package jetbrains.mps.ide.hierarchy;
/*Generated by MPS */
import javax.swing.JComponent;
import javax.swing.Scrollable;
import com.intellij.openapi.actionSystem.DataProvider;
import javax.swing.JPanel;
import org.jetbrains.mps.openapi.module.SModuleReference;
import jetbrains.mps.project.MPSProject;
import java.util.List;
import java.util.ArrayList;
import javax.swing.JTextField;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import javax.swing.JButton;
import javax.swing.AbstractAction;
import jetbrains.mps.ide.hierarchy.icons.Icons;
import java.awt.event.ActionEvent;
import javax.swing.JCheckBox;
import javax.swing.JScrollPane;
import com.intellij.ui.ScrollPaneFactory;
import javax.swing.border.LineBorder;
import org.jetbrains.mps.openapi.model.SNode;
import jetbrains.mps.workbench.action.BaseGroup;
import jetbrains.mps.workbench.action.ActionUtils;
import jetbrains.mps.ide.projectPane.ProjectPaneActionGroups;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import org.jetbrains.mps.openapi.module.SModule;
import jetbrains.mps.smodel.Language;
import org.jetbrains.mps.openapi.model.SModel;
import java.util.Map;
import java.util.HashMap;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SModelOperations;
import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SLinkOperations;
import java.awt.Dimension;
import java.awt.Container;
import javax.swing.JViewport;
import java.awt.Rectangle;
import java.awt.Graphics;
import javax.swing.SwingConstants;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.NonNls;
import jetbrains.mps.ide.actions.MPSCommonDataKeys;
import org.jetbrains.mps.openapi.model.SNodeReference;
import jetbrains.mps.ide.util.ColorAndGraphicsUtil;
import java.awt.Font;
import jetbrains.mps.nodeEditor.EditorSettings;
import java.awt.event.MouseListener;
import org.jetbrains.annotations.NotNull;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SPropertyOperations;
import jetbrains.mps.kernel.model.SModelUtil;
import jetbrains.mps.smodel.SNodePointer;
import jetbrains.mps.openapi.navigation.EditorNavigator;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.BasicStroke;
import java.awt.FontMetrics;
import jetbrains.mps.smodel.ModelAccessHelper;
import jetbrains.mps.util.Computable;
import java.util.Collections;
import java.util.Comparator;
import java.awt.Point;
public class LanguageHierarchiesComponent extends JComponent implements Scrollable, DataProvider {
private static final int SPACING = 10;
private static final int PADDING_X = 5;
private static final int PADDING_Y = 5;
private JPanel myPanel = new JPanel();
private final SModuleReference myLanguage;
private final MPSProject myProject;
private List<LanguageHierarchiesComponent.ConceptContainer> myRoots = new ArrayList<LanguageHierarchiesComponent.ConceptContainer>();
private float myScale = 1.0f;
private boolean mySkipAncestors = true;
private int myWidth = 0;
private int myHeight = 0;
private LanguageHierarchiesComponent.ConceptContainer mySelectedConceptContainer;
public JTextField myScaleField;
public LanguageHierarchiesComponent(SModuleReference language, MPSProject project) {
myLanguage = language;
myProject = project;
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
for (LanguageHierarchiesComponent.ConceptContainer conceptContainer : myRoots) {
conceptContainer.mouseClicked(e);
}
}
@Override
public void mousePressed(MouseEvent e) {
for (LanguageHierarchiesComponent.ConceptContainer conceptContainer : myRoots) {
conceptContainer.mousePressed(e);
}
}
@Override
public void mouseReleased(MouseEvent e) {
for (LanguageHierarchiesComponent.ConceptContainer conceptContainer : myRoots) {
conceptContainer.mouseReleased(e);
}
}
});
myPanel.setLayout(new BorderLayout());
myPanel.setBackground(Color.WHITE);
final JPanel toolsPane = new JPanel();
toolsPane.setLayout(new FlowLayout(FlowLayout.LEFT));
myScaleField = new JTextField("100%");
myScaleField.setEditable(false);
toolsPane.add(myScaleField);
toolsPane.add(new JButton(new AbstractAction(null, Icons.ZOOM_IN_ICON) {
@Override
public void actionPerformed(ActionEvent e) {
if (myScale < 6) {
myScale += 0.2f;
if (myScale > 6) {
myScale = 6;
}
myScaleField.setText((int) (myScale * 100) + "%");
relayout();
LanguageHierarchiesComponent.this.invalidate();
getExternalComponent().revalidate();
getExternalComponent().repaint();
}
}
}));
toolsPane.add(new JButton(new AbstractAction(null, Icons.ZOOM_OUT_ICON) {
@Override
public void actionPerformed(ActionEvent e) {
if (myScale > 0.2) {
myScale -= 0.2f;
if (myScale < 0.2) {
myScale = 0.2f;
}
myScaleField.setText((int) (myScale * 100) + "%");
relayout();
LanguageHierarchiesComponent.this.invalidate();
getExternalComponent().revalidate();
getExternalComponent().repaint();
}
}
}));
toolsPane.add(new JButton(new AbstractAction(null, Icons.ACTUAL_ZOOM_ICON) {
@Override
public void actionPerformed(ActionEvent e) {
if (myScale != 1) {
myScale = 1;
myScaleField.setText((int) (myScale * 100) + "%");
relayout();
LanguageHierarchiesComponent.this.invalidate();
getExternalComponent().revalidate();
getExternalComponent().repaint();
}
}
}));
final JCheckBox jCheckBox = new JCheckBox();
jCheckBox.setAction(new AbstractAction("Include Other Languages") {
@Override
public void actionPerformed(ActionEvent e) {
if (jCheckBox.getModel().isSelected()) {
if (mySkipAncestors) {
mySkipAncestors = false;
rebuild();
LanguageHierarchiesComponent.this.invalidate();
getExternalComponent().revalidate();
getExternalComponent().repaint();
}
} else {
if (!(mySkipAncestors)) {
mySkipAncestors = true;
rebuild();
LanguageHierarchiesComponent.this.invalidate();
getExternalComponent().revalidate();
getExternalComponent().repaint();
}
}
}
});
toolsPane.add(jCheckBox);
myPanel.add(toolsPane, BorderLayout.NORTH);
JScrollPane scrollPane = ScrollPaneFactory.createScrollPane();
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scrollPane.setViewportView(this);
scrollPane.setBorder(new LineBorder(Color.LIGHT_GRAY));
scrollPane.setBackground(Color.WHITE);
myPanel.add(scrollPane, BorderLayout.CENTER);
setBackground(Color.WHITE);
}
public JComponent getExternalComponent() {
return myPanel;
}
private void select(LanguageHierarchiesComponent.ConceptContainer conceptContainer) {
mySelectedConceptContainer = conceptContainer;
}
private SNode getSelectedConcept() {
if (mySelectedConceptContainer == null) {
return null;
}
return mySelectedConceptContainer.getNode();
}
private void processPopupMenu(MouseEvent e) {
BaseGroup group = ActionUtils.getGroup(ProjectPaneActionGroups.NODE_ACTIONS);
ActionManager.getInstance().createActionPopupMenu(ActionPlaces.UNKNOWN, group).getComponent().show(this, e.getX(), e.getY());
}
/**
* requires model read lock
*/
/*package*/ List<LanguageHierarchiesComponent.ConceptContainer> createHierarchyForest() {
List<LanguageHierarchiesComponent.ConceptContainer> result = new ArrayList<LanguageHierarchiesComponent.ConceptContainer>();
SModule resolved = myLanguage.resolve(myProject.getRepository());
if (!(resolved instanceof Language)) {
return result;
}
SModel structureModel = ((Language) resolved).getStructureModelDescriptor();
Map<SNode, LanguageHierarchiesComponent.ConceptContainer> processed = new HashMap<SNode, LanguageHierarchiesComponent.ConceptContainer>();
SNode baseConcept = SNodeOperations.getNode("r:00000000-0000-4000-0000-011c89590288(jetbrains.mps.lang.core.structure)", "1133920641626");
outer:
for (SNode concept : SModelOperations.roots(structureModel, MetaAdapterFactory.getConcept(0xc72da2b97cce4447L, 0x8389f407dc1158b7L, 0xf979ba0450L, "jetbrains.mps.lang.structure.structure.ConceptDeclaration"))) {
SNode parentConcept = concept;
LanguageHierarchiesComponent.ConceptContainer prevConceptContainer = null;
while (parentConcept != null && parentConcept != baseConcept && !((mySkipAncestors && SNodeOperations.getModel(parentConcept) != structureModel))) {
LanguageHierarchiesComponent.ConceptContainer newConceptContainer = processed.get(parentConcept);
if (newConceptContainer == null) {
newConceptContainer = new LanguageHierarchiesComponent.ConceptContainer(parentConcept, this, SNodeOperations.getModel(parentConcept) != structureModel);
}
newConceptContainer.addChild(prevConceptContainer);
prevConceptContainer = newConceptContainer;
if (processed.containsKey(parentConcept)) {
continue outer;
}
processed.put(parentConcept, newConceptContainer);
parentConcept = SLinkOperations.getTarget(parentConcept, MetaAdapterFactory.getReferenceLink(0xc72da2b97cce4447L, 0x8389f407dc1158b7L, 0xf979ba0450L, 0xf979be93cfL, "extends"));
}
if (prevConceptContainer != null) {
result.add(prevConceptContainer);
}
}
return result;
}
private void relayout() {
if (myRoots.isEmpty()) {
return;
}
int y = 0;
int x = 0;
int maxWidth = 0;
for (LanguageHierarchiesComponent.ConceptContainer root : myRoots) {
root.updateSubtreeWidth();
maxWidth = Math.max(maxWidth, root.getSubtreeWidth());
}
myHeight = relayoutChildren(myRoots, x, y, true);
myWidth = maxWidth;
}
private int relayoutChildren(List<LanguageHierarchiesComponent.ConceptContainer> currentChildren, int x, int y, boolean vertical) {
int y_ = y;
for (LanguageHierarchiesComponent.ConceptContainer root : currentChildren) {
int subtreeWidth = root.getSubtreeWidth();
root.setX(x + (subtreeWidth - root.getWidth()) / 2);
root.setY((int) (y + SPACING * myScale));
int newY = relayoutChildren(root.getChildren(), x, (int) (y + SPACING * myScale + root.getHeight()), false);
if (vertical) {
y = (int) (newY + root.getHeight() + 3 * SPACING * myScale);
y_ = y;
} else {
x += subtreeWidth;
y_ = (int) (Math.max(y_, Math.max(y + SPACING * myScale + root.getHeight(), newY)));
}
}
return y_;
}
public void rebuild() {
myProject.getModelAccess().runReadAction(new Runnable() {
@Override
public void run() {
mySelectedConceptContainer = null;
myRoots = createHierarchyForest();
relayout();
}
});
}
@Override
public Dimension getPreferredSize() {
Container parent = this;
while (parent != null) {
if (parent instanceof JViewport) {
break;
}
parent = parent.getParent();
}
if (parent == null) {
return new Dimension(myWidth, myHeight);
}
JViewport viewport = (JViewport) parent;
Rectangle viewRect = viewport.getViewRect();
return new Dimension(Math.max(viewRect.width, myWidth), Math.max(viewRect.height, myHeight));
}
@Override
protected void paintComponent(final Graphics g) {
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
for (LanguageHierarchiesComponent.ConceptContainer root : myRoots) {
root.paintTree(g);
}
}
@Override
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
@Override
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
if (orientation == SwingConstants.VERTICAL) {
return 20;
} else {
return 20;
}
}
@Override
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
return visibleRect.height;
}
@Override
public boolean getScrollableTracksViewportWidth() {
return false;
}
@Override
public boolean getScrollableTracksViewportHeight() {
return false;
}
@Nullable
@Override
public Object getData(@NonNls String dataId) {
if (MPSCommonDataKeys.NODE.is(dataId)) {
return getSelectedConcept();
}
return null;
}
public static class ConceptContainer {
private SNodeReference myNodePointer;
private int myX;
private int myY;
private int myWidth;
private int myHeight;
private Color myColor = ColorAndGraphicsUtil.saturateColor(Color.BLUE, 0.2f);
private boolean myRootable = false;
private int mySubtreeWidth = 0;
private List<LanguageHierarchiesComponent.ConceptContainer> myChildren = new ArrayList<LanguageHierarchiesComponent.ConceptContainer>();
private LanguageHierarchiesComponent.ConceptContainer myParent;
private Font myFont = EditorSettings.getInstance().getDefaultEditorFont().deriveFont(Font.PLAIN, 12.0f);
private LanguageHierarchiesComponent myComponent;
private List<MouseListener> myMouseListeners = new ArrayList<MouseListener>();
private boolean myIsAbstract = false;
private String myNamespace;
private boolean myIsOtherLanguage = false;
public ConceptContainer(@NotNull SNode conceptDeclaration, LanguageHierarchiesComponent component, boolean otherLanguage) {
myComponent = component;
myIsOtherLanguage = otherLanguage;
if (myIsOtherLanguage) {
myColor = ColorAndGraphicsUtil.saturateColor(Color.ORANGE, 0.5f);
}
myRootable = SPropertyOperations.getBoolean(conceptDeclaration, MetaAdapterFactory.getProperty(0xc72da2b97cce4447L, 0x8389f407dc1158b7L, 0xf979ba0450L, 0xff49c1d648L, "rootable"));
myIsAbstract = SPropertyOperations.getBoolean(conceptDeclaration, MetaAdapterFactory.getProperty(0xc72da2b97cce4447L, 0x8389f407dc1158b7L, 0x1103553c5ffL, 0x403a32c5772c7ec2L, "abstract"));
myNamespace = SModelUtil.getDeclaringLanguage(conceptDeclaration).getModuleName();
myNodePointer = new SNodePointer(conceptDeclaration);
addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
myComponent.select(ConceptContainer.this);
if (e.isPopupTrigger()) {
myComponent.processPopupMenu(e);
} else if (e.getClickCount() == 2) {
new EditorNavigator(myComponent.myProject).shallFocus(true).shallSelect(true).open(myNodePointer);
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger()) {
myComponent.processPopupMenu(e);
}
}
});
}
public SNode getNode() {
return SNodeOperations.cast(myNodePointer.resolve(myComponent.myProject.getRepository()), MetaAdapterFactory.getConcept(0xc72da2b97cce4447L, 0x8389f407dc1158b7L, 0xf979ba0450L, "jetbrains.mps.lang.structure.structure.ConceptDeclaration"));
}
public void paint(Graphics graphics) {
Graphics2D g = (Graphics2D) graphics;
Color color = myColor;
g.setColor(color);
g.fillRect(myX, myY, myWidth, myHeight);
g.setColor(Color.black);
Stroke oldStroke = g.getStroke();
if (myRootable) {
g.setStroke(new BasicStroke(3));
}
g.drawRect(myX, myY, myWidth, myHeight);
Font font = myFont.deriveFont((myIsAbstract ? Font.ITALIC : Font.PLAIN), (float) myFont.getSize() * myComponent.myScale);
FontMetrics metrics = myComponent.getFontMetrics(font);
String text = getText();
int padding1 = (myWidth - metrics.charsWidth(text.toCharArray(), 0, text.length())) / 2;
int padding2 = (myWidth - metrics.charsWidth(myNamespace.toCharArray(), 0, myNamespace.length())) / 2;
int x1 = (int) (myX + padding1);
int x2 = (int) (myX + padding2);
int y = (int) (myY + (myHeight - metrics.getHeight()) / 2);
Font oldfont = g.getFont();
g.setFont(font);
g.drawString(text, x1, y + metrics.getAscent());
if (myIsOtherLanguage) {
g.setFont(font.deriveFont(Font.PLAIN));
g.drawString(myNamespace, x2, y + metrics.getHeight() + metrics.getAscent());
}
g.setFont(oldfont);
g.setStroke(oldStroke);
}
@NotNull
public String getText() {
return new ModelAccessHelper(myComponent.myProject.getModelAccess()).runReadAction(new Computable<String>() {
@Override
public String compute() {
SNode conceptDeclaration = getNode();
if (conceptDeclaration == null) {
return "";
}
String name = SPropertyOperations.getString(conceptDeclaration, MetaAdapterFactory.getProperty(0xceab519525ea4f22L, 0x9b92103b95ca8c0cL, 0x110396eaaa4L, 0x110396ec041L, "name"));
return (name != null ? name : "");
}
});
}
public List<LanguageHierarchiesComponent.ConceptContainer> getChildren() {
return new ArrayList<LanguageHierarchiesComponent.ConceptContainer>(myChildren);
}
public void addChild(LanguageHierarchiesComponent.ConceptContainer child) {
if (child == null) {
return;
}
myChildren.add(child);
child.myParent = this;
}
public LanguageHierarchiesComponent.ConceptContainer getParent() {
return myParent;
}
public void sortSubtree() {
Collections.sort(myChildren, new Comparator<LanguageHierarchiesComponent.ConceptContainer>() {
@Override
public int compare(LanguageHierarchiesComponent.ConceptContainer o1, LanguageHierarchiesComponent.ConceptContainer o2) {
return o1.getText().compareTo(o2.getText());
}
});
for (LanguageHierarchiesComponent.ConceptContainer child : myChildren) {
child.sortSubtree();
}
}
public void updateSubtreeWidth() {
updateSize();
int sum = 0;
for (LanguageHierarchiesComponent.ConceptContainer conceptContainer : myChildren) {
conceptContainer.updateSubtreeWidth();
sum += conceptContainer.mySubtreeWidth;
}
mySubtreeWidth = (int) (Math.max(sum, myWidth + 2 * LanguageHierarchiesComponent.SPACING * myComponent.myScale));
if (sum < mySubtreeWidth) {
Map<LanguageHierarchiesComponent.ConceptContainer, Integer> sizes = new HashMap<LanguageHierarchiesComponent.ConceptContainer, Integer>();
computeSubtreeSizes(sizes);
updateSubtreeWidth1(sizes);
}
}
private void updateSubtreeWidth1(Map<LanguageHierarchiesComponent.ConceptContainer, Integer> sizes) {
int whole = sizes.get(this) - 1;
for (LanguageHierarchiesComponent.ConceptContainer child : myChildren) {
child.mySubtreeWidth = (mySubtreeWidth * sizes.get(child)) / whole;
child.updateSubtreeWidth1(sizes);
}
}
private int computeSubtreeSizes(Map<LanguageHierarchiesComponent.ConceptContainer, Integer> sizes) {
int size = 1;
for (LanguageHierarchiesComponent.ConceptContainer child : myChildren) {
size += child.computeSubtreeSizes(sizes);
}
sizes.put(this, size);
return size;
}
public int getSubtreeWidth() {
return mySubtreeWidth;
}
public void updateSize() {
Font font = myFont.deriveFont((float) myFont.getSize() * myComponent.myScale);
FontMetrics metrics = myComponent.getFontMetrics(font);
String text = getText();
int charsWidth1 = metrics.charsWidth(text.toCharArray(), 0, text.length());
int charWidth2 = (myIsOtherLanguage ? metrics.charsWidth(myNamespace.toCharArray(), 0, myNamespace.length()) : 0);
int charsHeight = metrics.getHeight();
if (myIsOtherLanguage) {
charsHeight = charsHeight * 2 + metrics.getAscent();
}
myHeight = (int) ((2 * LanguageHierarchiesComponent.PADDING_Y * myComponent.myScale) + charsHeight);
myWidth = (int) ((2 * LanguageHierarchiesComponent.PADDING_X * myComponent.myScale) + Math.max(charsWidth1, charWidth2));
}
public int getWidth() {
return myWidth;
}
public int getHeight() {
return myHeight;
}
public int getX() {
return myX;
}
public int getY() {
return myY;
}
public void setX(int x) {
myX = x;
}
public void setY(int y) {
myY = y;
}
public void moveTo(int newX, int newY) {
int deltaX = newX - myX;
int deltaY = newY - myY;
myX = newX;
myY = newY;
for (LanguageHierarchiesComponent.ConceptContainer child : myChildren) {
child.moveTo(child.getX() + deltaX, child.getY() + deltaY);
}
}
public Point getEntryPoint() {
return new Point(myX + myWidth / 2, myY);
}
public Point getOutPoint() {
return new Point(myX + myWidth / 2, myY + myHeight);
}
public void paintTree(Graphics g) {
paint(g);
if (myChildren.isEmpty()) {
return;
}
int outX = getOutPoint().x;
int outY = getOutPoint().y;
int y = outY;
int firstX = outX;
int lastX = outX;
for (LanguageHierarchiesComponent.ConceptContainer child : myChildren) {
Point childEntryPoint = child.getEntryPoint();
int x = childEntryPoint.x;
if (x < firstX) {
firstX = x;
}
if (x > lastX) {
lastX = x;
}
y = childEntryPoint.y;
}
y = (y + outY) / 2;
g.setColor(Color.BLACK);
g.drawLine(firstX, y, lastX, y);
g.drawLine(outX, outY, outX, y);
for (LanguageHierarchiesComponent.ConceptContainer child : myChildren) {
g.setColor(Color.BLACK);
Point childEntryPoint = child.getEntryPoint();
g.drawLine(childEntryPoint.x, y, childEntryPoint.x, childEntryPoint.y);
child.paintTree(g);
}
}
protected boolean mouseClicked(MouseEvent ev) {
if (checkMouseEvent(ev)) {
for (MouseListener listener : myMouseListeners) {
listener.mouseClicked(ev);
}
return true;
}
for (LanguageHierarchiesComponent.ConceptContainer child : getChildren()) {
if (child.mouseClicked(ev)) {
return true;
}
}
return false;
}
protected boolean mousePressed(MouseEvent ev) {
if (checkMouseEvent(ev)) {
for (MouseListener listener : myMouseListeners) {
listener.mousePressed(ev);
}
return true;
}
for (LanguageHierarchiesComponent.ConceptContainer child : getChildren()) {
if (child.mousePressed(ev)) {
return true;
}
}
return false;
}
protected boolean mouseReleased(MouseEvent ev) {
if (checkMouseEvent(ev)) {
for (MouseListener listener : myMouseListeners) {
listener.mouseReleased(ev);
}
return true;
}
for (LanguageHierarchiesComponent.ConceptContainer child : getChildren()) {
if (child.mouseReleased(ev)) {
return true;
}
}
return false;
}
protected boolean checkMouseEvent(MouseEvent ev) {
int x = ev.getX();
int y = ev.getY();
if (x > myX + myWidth) {
return false;
}
if (x < myX) {
return false;
}
if (y > myY + myHeight) {
return false;
}
if (y < myY) {
return false;
}
return true;
}
public void setColor(Color c) {
myColor = c;
}
public void addMouseListener(MouseListener listener) {
myMouseListeners.add(listener);
}
public void removeMouseListener(MouseListener listener) {
myMouseListeners.remove(listener);
}
}
}