All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.sslr.internal.toolkit.ToolkitViewImpl Maven / Gradle / Ivy

The newest version!
/*
 * SonarSource Language Recognizer
 * Copyright (C) 2010-2020 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.sslr.internal.toolkit;

import com.sonar.sslr.api.AstNode;
import com.sonar.sslr.api.Token;
import com.sonar.sslr.api.Trivia;

import javax.annotation.Nullable;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JEditorPane;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultCaret;
import javax.swing.text.DefaultHighlighter;
import javax.swing.text.Element;
import javax.swing.text.html.HTMLDocument;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public class ToolkitViewImpl extends JFrame implements ToolkitView {

  private static final long serialVersionUID = 1L;
  private static final TreeModel EMPTY_TREE_MODEL = new DefaultTreeModel(null);

  public final transient ToolkitPresenter presenter;

  private final JTabbedPane tabbedPane = new JTabbedPane();

  private final JTree astTree = new JTree();
  private final JScrollPane astTreeScrollPane = new JScrollPane(astTree);

  private final JTextArea xmlTextArea = new JTextArea();
  private final JScrollPane xmlScrollPane = new JScrollPane(xmlTextArea);

  private final JTextArea consoleTextArea = new JTextArea();
  private final JScrollPane consoleScrollPane = new JScrollPane(consoleTextArea);

  private final JPanel configurationInnerPanel = new JPanel(new GridBagLayout());
  private final JPanel configurationOuterPanel = new JPanel(new BorderLayout());
  private final JScrollPane configurationScrollPane = new JScrollPane(configurationOuterPanel);
  private final Map configurationPropertiesPanels = new HashMap<>();

  private final JLabel sourceCodeLabel = new JLabel(" Source Code");
  private final JEditorPane sourceCodeEditorPane = new JEditorPane();
  private final JScrollPane sourceCodeEditorScrollPane = new JScrollPane(sourceCodeEditorPane);
  private final JButton sourceCodeOpenButton = new JButton();
  private final JButton sourceCodeParseButton = new JButton();
  private final JPanel sourceCodeButtonsPanel = new JPanel();
  private final JPanel sourceCodePanel = new JPanel(new BorderLayout(0, 2));

  private final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, sourceCodePanel, tabbedPane);

  private final JPanel southPanel = new JPanel(new BorderLayout(0, 2));

  private final JLabel xpathLabel = new JLabel("  XPath query");
  private final JTextArea xpathTextArea = new JTextArea();
  private final JScrollPane xpathTextAreaScrollPane = new JScrollPane(xpathTextArea);
  private final JPanel xpathPanel = new JPanel(new BorderLayout(10, 2));

  private final JFileChooser fileChooser = new JFileChooser();
  private final JButton xpathButton = new JButton();
  private final JPanel xpathButtonPanel = new JPanel();

  private transient LineOffsets lineOffsets = null;
  private final transient DefaultHighlighter.DefaultHighlightPainter highlighter = new DefaultHighlighter.DefaultHighlightPainter(Color.LIGHT_GRAY);

  private boolean sourceCodeTextCursorMovedEventDisabled = false;
  private boolean astSelectionEventDisabled = false;

  public ToolkitViewImpl(ToolkitPresenter presenter) {
    Objects.requireNonNull(presenter);
    this.presenter = presenter;

    initComponents();
  }

  private void initComponents() {
    setSize(1000, 700);
    setDefaultCloseOperation(ToolkitViewImpl.EXIT_ON_CLOSE);

    setLayout(new BorderLayout(0, 5));

    astTree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
    astTree.addTreeSelectionListener(new TreeSelectionListener() {
      @Override
      public void valueChanged(TreeSelectionEvent event) {
        if (!astSelectionEventDisabled) {
          presenter.onAstSelectionChanged();
        }
      }
    });

    consoleTextArea.setEditable(false);
    consoleTextArea.setFont(Font.decode("Monospaced"));

    tabbedPane.setTabPlacement(JTabbedPane.TOP);
    tabbedPane.add("Abstract Syntax Tree", astTreeScrollPane);
    tabbedPane.add("XML", xmlScrollPane);
    tabbedPane.add("Console", consoleScrollPane);
    tabbedPane.add("Configuration", configurationScrollPane);

    configurationOuterPanel.add(configurationInnerPanel, BorderLayout.NORTH);
    configurationOuterPanel.add(Box.createGlue(), BorderLayout.CENTER);

    sourceCodeEditorPane.setContentType("text/html");
    sourceCodeEditorPane.setEditable(true);
    ((DefaultCaret) sourceCodeEditorPane.getCaret()).setUpdatePolicy(DefaultCaret.UPDATE_WHEN_ON_EDT);
    sourceCodeEditorPane.getDocument().addDocumentListener(new DocumentListener() {
      @Override
      public void removeUpdate(DocumentEvent e) {
        presenter.onSourceCodeKeyTyped();
      }

      @Override
      public void insertUpdate(DocumentEvent e) {
        presenter.onSourceCodeKeyTyped();
      }

      @Override
      public void changedUpdate(DocumentEvent e) {
        presenter.onSourceCodeKeyTyped();
      }
    });
    sourceCodeEditorPane.addCaretListener(new CaretListener() {
      @Override
      public void caretUpdate(CaretEvent e) {
        if (!sourceCodeTextCursorMovedEventDisabled) {
          presenter.onSourceCodeTextCursorMoved();
        }
      }
    });

    sourceCodeOpenButton.setText("Open Source File");
    sourceCodeOpenButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        presenter.onSourceCodeOpenButtonClick();
      }
    });

    sourceCodeParseButton.setText("Parse Source Code");
    sourceCodeParseButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        presenter.onSourceCodeParseButtonClick();
      }
    });

    sourceCodeButtonsPanel.add(sourceCodeOpenButton);
    sourceCodeButtonsPanel.add(sourceCodeParseButton);

    sourceCodePanel.add(sourceCodeLabel, BorderLayout.NORTH);
    sourceCodePanel.add(sourceCodeEditorScrollPane, BorderLayout.CENTER);
    sourceCodePanel.add(sourceCodeButtonsPanel, BorderLayout.SOUTH);

    splitPane.setDividerLocation(getWidth() / 2);
    add(splitPane, BorderLayout.CENTER);

    xpathPanel.add(xpathLabel, BorderLayout.NORTH);
    xpathPanel.add(Box.createHorizontalGlue(), BorderLayout.WEST);
    xpathTextArea.setText("//IDENTIFIER");
    xpathTextArea.setRows(8);
    xpathPanel.add(xpathTextAreaScrollPane, BorderLayout.CENTER);
    xpathPanel.add(Box.createHorizontalGlue(), BorderLayout.EAST);

    southPanel.add(xpathPanel, BorderLayout.NORTH);

    xpathButton.setText("Evaluate XPath");
    xpathButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        presenter.onXPathEvaluateButtonClick();
      }
    });
    xpathButtonPanel.add(xpathButton);

    southPanel.add(xpathButtonPanel, BorderLayout.SOUTH);

    add(southPanel, BorderLayout.SOUTH);
  }

  @Override
  public void run() {
    setVisible(true);
  }

  @Override
  public File pickFileToParse() {
    if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
      return fileChooser.getSelectedFile();
    } else {
      return null;
    }
  }

  @Override
  public void displayHighlightedSourceCode(String htmlHighlightedSourceCode) {
    try {
      sourceCodeTextCursorMovedEventDisabled = true;

      Objects.requireNonNull(htmlHighlightedSourceCode);

      StringBuilder sb = new StringBuilder();
      sb.append("
");
      sb.append(htmlHighlightedSourceCode);
      sb.append("
"); sourceCodeEditorPane.setText(sb.toString()); lineOffsets = new LineOffsets(getSourceCode()); } finally { sourceCodeTextCursorMovedEventDisabled = false; } } @Override public void displayAst(@Nullable AstNode astNode) { if (astNode == null) { astTree.setModel(EMPTY_TREE_MODEL); } else { TreeNode treeNode = getTreeNode(astNode); astTree.setModel(new DefaultTreeModel(treeNode)); } } private static DefaultMutableTreeNode getTreeNode(AstNode astNode) { DefaultMutableTreeNode treeNode = new DefaultMutableTreeNode(astNode); if (astNode.hasChildren()) { for (AstNode childAstNode : astNode.getChildren()) { treeNode.add(getTreeNode(childAstNode)); } } else if (astNode.hasToken() && astNode.getToken().hasTrivia()) { for (Trivia trivia : astNode.getToken().getTrivia()) { DefaultMutableTreeNode childTreeNode = new DefaultMutableTreeNode(trivia); if (trivia.hasPreprocessingDirective()) { childTreeNode.add(getTreeNode(trivia.getPreprocessingDirective().getAst())); } treeNode.add(childTreeNode); } } return treeNode; } @Override public void displayXml(String xml) { Objects.requireNonNull(xml); xmlTextArea.setText(xml); } @Override public Point getSourceCodeScrollbarPosition() { int x = sourceCodeEditorScrollPane.getHorizontalScrollBar().getValue(); int y = sourceCodeEditorScrollPane.getVerticalScrollBar().getValue(); return new Point(x, y); } @Override public void scrollSourceCodeTo(final Point point) { Objects.requireNonNull(point); // http://stackoverflow.com/questions/8789371/java-jtextpane-jscrollpane-de-activate-automatic-scrolling SwingUtilities.invokeLater(new Runnable() { @Override public void run() { sourceCodeEditorScrollPane.getHorizontalScrollBar().setValue(point.x); sourceCodeEditorScrollPane.getVerticalScrollBar().setValue(point.y); } }); } @Override public String getSourceCode() { int startOffset = getCodeElementStartOffset(); int endOffset = getCodeElementEndOffset(); try { return sourceCodeEditorPane.getText(startOffset, endOffset - startOffset - 1); } catch (BadLocationException e) { throw new RuntimeException(e); } } private int getCodeElementStartOffset() { HTMLDocument htmlDocument = (HTMLDocument) sourceCodeEditorPane.getDocument(); Element codeElement = htmlDocument.getElement("code"); return codeElement.getStartOffset(); } private int getCodeElementEndOffset() { HTMLDocument htmlDocument = (HTMLDocument) sourceCodeEditorPane.getDocument(); Element codeElement = htmlDocument.getElement("code"); return codeElement.getEndOffset(); } @Override public String getXPath() { return xpathTextArea.getText(); } @Override public void selectAstNode(AstNode astNode) { if (astNode != null) { try { astSelectionEventDisabled = true; DefaultMutableTreeNode treeNode = getAstTreeNodeWithGivenUserObject((DefaultMutableTreeNode) astTree.getModel().getRoot(), astNode); astTree.getSelectionModel().addSelectionPath(new TreePath(treeNode.getPath())); } finally { astSelectionEventDisabled = false; } } } private DefaultMutableTreeNode getAstTreeNodeWithGivenUserObject(DefaultMutableTreeNode treeNode, Object userObject) { if (treeNode.getUserObject().equals(userObject)) { return treeNode; } else { for (int i = 0; i < treeNode.getChildCount(); i++) { DefaultMutableTreeNode treeNodeWithUserObject = getAstTreeNodeWithGivenUserObject((DefaultMutableTreeNode) treeNode.getChildAt(i), userObject); if (treeNodeWithUserObject != null) { return treeNodeWithUserObject; } } return null; } } @Override public void highlightSourceCode(AstNode astNode) { Objects.requireNonNull(astNode); if (!astNode.hasToken()) { return; } Token startToken = astNode.getToken(); Token endToken = astNode.getLastToken(); int startOffset = getValidDocumentOffsetFromSourceCodeOffset(lineOffsets.getStartOffset(startToken)); int endOffset = getValidDocumentOffsetFromSourceCodeOffset(lineOffsets.getEndOffset(endToken)); try { sourceCodeEditorPane.getHighlighter().addHighlight(startOffset, endOffset, highlighter); } catch (BadLocationException e) { throw new RuntimeException(e); } } private int getValidDocumentOffsetFromSourceCodeOffset(int offset) { int result = Math.max(offset, 0); result += getCodeElementStartOffset(); result = Math.min(result, getCodeElementEndOffset()); return result; } @Override public void clearAstSelections() { try { astSelectionEventDisabled = true; astTree.getSelectionModel().clearSelection(); } finally { astSelectionEventDisabled = false; } } @Override public void scrollAstTo(@Nullable AstNode astNode) { if (astNode != null) { DefaultMutableTreeNode treeNode = getAstTreeNodeWithGivenUserObject((DefaultMutableTreeNode) astTree.getModel().getRoot(), astNode); astTree.scrollPathToVisible(new TreePath(treeNode.getPath())); } } @Override public void clearSourceCodeHighlights() { sourceCodeEditorPane.getHighlighter().removeAllHighlights(); } @Override public void scrollSourceCodeTo(@Nullable AstNode astNode) { if (astNode != null && astNode.hasToken()) { int visibleLines = sourceCodeEditorPane.getVisibleRect().height / sourceCodeEditorPane.getFontMetrics(sourceCodeEditorPane.getFont()).getHeight(); int line = astNode.getToken().getLine() + visibleLines / 2; try { sourceCodeEditorPane.scrollRectToVisible(sourceCodeEditorPane.modelToView(0)); sourceCodeEditorPane.scrollRectToVisible(sourceCodeEditorPane.modelToView(lineOffsets.getOffset(line, 0))); } catch (BadLocationException e) { throw new RuntimeException(e); } } } @Override public void disableXPathEvaluateButton() { xpathButton.setEnabled(false); } @Override public void enableXPathEvaluateButton() { xpathButton.setEnabled(true); } @Override @Nullable public AstNode getAstNodeFollowingCurrentSourceCodeTextCursorPosition() { int currentOffset = sourceCodeEditorPane.getCaretPosition() - getCodeElementStartOffset(); return getFollowingAstNode((DefaultMutableTreeNode) astTree.getModel().getRoot(), currentOffset); } private AstNode getFollowingAstNode(DefaultMutableTreeNode treeNode, int offset) { AstNode followingAstNode = null; if (treeNode != null) { Enumeration enumeration = ((DefaultMutableTreeNode) astTree.getModel().getRoot()).breadthFirstEnumeration(); int nearestOffsetSoFar = Integer.MAX_VALUE; while (enumeration.hasMoreElements()) { DefaultMutableTreeNode childTreeNode = (DefaultMutableTreeNode) enumeration.nextElement(); if (childTreeNode.getUserObject() instanceof AstNode) { AstNode astNode = (AstNode) childTreeNode.getUserObject(); if (astNode.hasToken()) { Token token = astNode.getToken(); int tokenOffset = lineOffsets.getStartOffset(token); if (tokenOffset >= offset && tokenOffset < nearestOffsetSoFar) { nearestOffsetSoFar = tokenOffset; followingAstNode = astNode; } } } } } return followingAstNode; } @Override public List getSelectedAstNodes() { List acc = new ArrayList<>(); TreePath[] selectedPaths = astTree.getSelectionPaths(); if (selectedPaths != null) { for (TreePath selectedPath : selectedPaths) { DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) selectedPath.getLastPathComponent(); Object userObject = treeNode.getUserObject(); if (userObject instanceof AstNode) { AstNode astNode = (AstNode) userObject; acc.add(astNode); } } } return acc; } @Override public void appendToConsole(String message) { consoleTextArea.append(message); } @Override public void setFocusOnConsoleView() { tabbedPane.setSelectedComponent(consoleScrollPane); } @Override public void setFocusOnAbstractSyntaxTreeView() { tabbedPane.setSelectedComponent(astTreeScrollPane); } @Override public void clearConsole() { consoleTextArea.setText(""); } @Override public void addConfigurationProperty(final String name, String description) { ConfigurationPropertyPanel configurationPropertyPanel = new ConfigurationPropertyPanel(name, description); configurationPropertyPanel.getValueTextField().addFocusListener(new FocusAdapter() { @Override public void focusLost(FocusEvent e) { presenter.onConfigurationPropertyFocusLost(name); } }); configurationPropertiesPanels.put(name, configurationPropertyPanel); GridBagConstraints constraints = new GridBagConstraints(); constraints.fill = GridBagConstraints.HORIZONTAL; constraints.weightx = 1; constraints.gridx = 0; constraints.anchor = GridBagConstraints.NORTH; configurationInnerPanel.add(configurationPropertyPanel.getPanel(), constraints); } @Override public String getConfigurationPropertyValue(String name) { return configurationPropertiesPanels.get(name).getValueTextField().getText(); } @Override public void setConfigurationPropertyValue(String name, String value) { configurationPropertiesPanels.get(name).getValueTextField().setText(value); } @Override public void setConfigurationPropertyErrorMessage(String name, String errorMessage) { configurationPropertiesPanels.get(name).getErrorMessageLabel().setText(errorMessage); } @Override public void setFocusOnConfigurationPropertyField(String name) { configurationPropertiesPanels.get(name).getValueTextField().requestFocus(); } @Override public void setFocusOnConfigurationView() { tabbedPane.setSelectedComponent(configurationScrollPane); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy