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); } } }