diff options
author | Julien HENRY <julien.henry@sonarsource.com> | 2015-10-14 15:59:51 +0200 |
---|---|---|
committer | Julien HENRY <julien.henry@sonarsource.com> | 2015-11-09 08:40:05 +0100 |
commit | eb5fd4fe77eb1b82b3aa5e1c3474d85ac39ee9ce (patch) | |
tree | 63ad2b709ae01f8fb7fe47540df792c2cc9a1d91 | |
parent | 7567526369d1082a8023623c016b970cac314c64 (diff) | |
download | sonarqube-eb5fd4fe77eb1b82b3aa5e1c3474d85ac39ee9ce.tar.gz sonarqube-eb5fd4fe77eb1b82b3aa5e1c3474d85ac39ee9ce.zip |
SONAR-6922 Initial version of report viewer
3 files changed, 806 insertions, 0 deletions
diff --git a/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/output/BatchReportReader.java b/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/output/BatchReportReader.java index cdb1d542fe7..786fcaaa9fb 100644 --- a/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/output/BatchReportReader.java +++ b/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/output/BatchReportReader.java @@ -120,6 +120,11 @@ public class BatchReportReader { return emptyCloseableIterator(); } + public boolean hasCoverage(int componentRef) { + File file = fileStructure.fileFor(FileStructure.Domain.COVERAGES, componentRef); + return file.exists(); + } + public CloseableIterator<BatchReport.Coverage> readComponentCoverage(int fileRef) { File file = fileStructure.fileFor(FileStructure.Domain.COVERAGES, fileRef); if (fileExists(file)) { diff --git a/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/viewer/TextLineNumber.java b/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/viewer/TextLineNumber.java new file mode 100644 index 00000000000..f0aa1e3215b --- /dev/null +++ b/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/viewer/TextLineNumber.java @@ -0,0 +1,460 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.batch.protocol.viewer; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Rectangle; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.HashMap; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import javax.swing.border.Border; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; +import javax.swing.border.MatteBorder; +import javax.swing.event.CaretEvent; +import javax.swing.event.CaretListener; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.Element; +import javax.swing.text.JTextComponent; +import javax.swing.text.StyleConstants; +import javax.swing.text.Utilities; + +/** + * This class will display line numbers for a related text component. The text + * component must use the same line height for each line. TextLineNumber + * supports wrapped lines and will highlight the line number of the current + * line in the text component. + * + * This class was designed to be used as a component added to the row header + * of a JScrollPane. + */ +public class TextLineNumber extends JPanel + implements CaretListener, DocumentListener, PropertyChangeListener { + public final static float LEFT = 0.0f; + public final static float CENTER = 0.5f; + public final static float RIGHT = 1.0f; + + private final static Border OUTER = new MatteBorder(0, 0, 0, 2, Color.GRAY); + + private final static int HEIGHT = Integer.MAX_VALUE - 1000000; + + // Text component this TextTextLineNumber component is in sync with + + private JTextComponent component; + + // Properties that can be changed + + private boolean updateFont; + private int borderGap; + private Color currentLineForeground; + private float digitAlignment; + private int minimumDisplayDigits; + + // Keep history information to reduce the number of times the component + // needs to be repainted + + private int lastDigits; + private int lastHeight; + private int lastLine; + + private HashMap<String, FontMetrics> fonts; + + /** + * Create a line number component for a text component. This minimum + * display width will be based on 3 digits. + * + * @param component the related text component + */ + public TextLineNumber(JTextComponent component) { + this(component, 3); + } + + /** + * Create a line number component for a text component. + * + * @param component the related text component + * @param minimumDisplayDigits the number of digits used to calculate + * the minimum width of the component + */ + public TextLineNumber(JTextComponent component, int minimumDisplayDigits) { + this.component = component; + + setFont(component.getFont()); + + setBorderGap(5); + setCurrentLineForeground(Color.RED); + setDigitAlignment(RIGHT); + setMinimumDisplayDigits(minimumDisplayDigits); + + component.getDocument().addDocumentListener(this); + component.addCaretListener(this); + component.addPropertyChangeListener("font", this); + } + + /** + * Gets the update font property + * + * @return the update font property + */ + public boolean getUpdateFont() { + return updateFont; + } + + /** + * Set the update font property. Indicates whether this Font should be + * updated automatically when the Font of the related text component + * is changed. + * + * @param updateFont when true update the Font and repaint the line + * numbers, otherwise just repaint the line numbers. + */ + public void setUpdateFont(boolean updateFont) { + this.updateFont = updateFont; + } + + /** + * Gets the border gap + * + * @return the border gap in pixels + */ + public int getBorderGap() { + return borderGap; + } + + /** + * The border gap is used in calculating the left and right insets of the + * border. Default value is 5. + * + * @param borderGap the gap in pixels + */ + public void setBorderGap(int borderGap) { + this.borderGap = borderGap; + Border inner = new EmptyBorder(0, borderGap, 0, borderGap); + setBorder(new CompoundBorder(OUTER, inner)); + lastDigits = 0; + setPreferredWidth(); + } + + /** + * Gets the current line rendering Color + * + * @return the Color used to render the current line number + */ + public Color getCurrentLineForeground() { + return currentLineForeground == null ? getForeground() : currentLineForeground; + } + + /** + * The Color used to render the current line digits. Default is Coolor.RED. + * + * @param currentLineForeground the Color used to render the current line + */ + public void setCurrentLineForeground(Color currentLineForeground) { + this.currentLineForeground = currentLineForeground; + } + + /** + * Gets the digit alignment + * + * @return the alignment of the painted digits + */ + public float getDigitAlignment() { + return digitAlignment; + } + + /** + * Specify the horizontal alignment of the digits within the component. + * Common values would be: + * <ul> + * <li>TextLineNumber.LEFT + * <li>TextLineNumber.CENTER + * <li>TextLineNumber.RIGHT (default) + * </ul> + * @param currentLineForeground the Color used to render the current line + */ + public void setDigitAlignment(float digitAlignment) { + this.digitAlignment = digitAlignment > 1.0f ? 1.0f : digitAlignment < 0.0f ? -1.0f : digitAlignment; + } + + /** + * Gets the minimum display digits + * + * @return the minimum display digits + */ + public int getMinimumDisplayDigits() { + return minimumDisplayDigits; + } + + /** + * Specify the mimimum number of digits used to calculate the preferred + * width of the component. Default is 3. + * + * @param minimumDisplayDigits the number digits used in the preferred + * width calculation + */ + public void setMinimumDisplayDigits(int minimumDisplayDigits) { + this.minimumDisplayDigits = minimumDisplayDigits; + setPreferredWidth(); + } + + /** + * Calculate the width needed to display the maximum line number + */ + private void setPreferredWidth() { + Element root = component.getDocument().getDefaultRootElement(); + int lines = root.getElementCount(); + int digits = Math.max(String.valueOf(lines).length(), minimumDisplayDigits); + + // Update sizes when number of digits in the line number changes + + if (lastDigits != digits) { + lastDigits = digits; + FontMetrics fontMetrics = getFontMetrics(getFont()); + int width = fontMetrics.charWidth('0') * digits; + Insets insets = getInsets(); + int preferredWidth = insets.left + insets.right + width; + + Dimension d = getPreferredSize(); + d.setSize(preferredWidth, HEIGHT); + setPreferredSize(d); + setSize(d); + } + } + + /** + * Draw the line numbers + */ + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + + // Determine the width of the space available to draw the line number + + FontMetrics fontMetrics = component.getFontMetrics(component.getFont()); + Insets insets = getInsets(); + int availableWidth = getSize().width - insets.left - insets.right; + + // Determine the rows to draw within the clipped bounds. + + Rectangle clip = g.getClipBounds(); + int rowStartOffset = component.viewToModel(new Point(0, clip.y)); + int endOffset = component.viewToModel(new Point(0, clip.y + clip.height)); + + while (rowStartOffset <= endOffset) { + try { + if (isCurrentLine(rowStartOffset)) + g.setColor(getCurrentLineForeground()); + else + g.setColor(getForeground()); + + // Get the line number as a string and then determine the + // "X" and "Y" offsets for drawing the string. + + String lineNumber = getTextLineNumber(rowStartOffset); + int stringWidth = fontMetrics.stringWidth(lineNumber); + int x = getOffsetX(availableWidth, stringWidth) + insets.left; + int y = getOffsetY(rowStartOffset, fontMetrics); + g.drawString(lineNumber, x, y); + + // Move to the next row + + rowStartOffset = Utilities.getRowEnd(component, rowStartOffset) + 1; + } catch (Exception e) { + break; + } + } + } + + /* + * We need to know if the caret is currently positioned on the line we + * are about to paint so the line number can be highlighted. + */ + private boolean isCurrentLine(int rowStartOffset) { + int caretPosition = component.getCaretPosition(); + Element root = component.getDocument().getDefaultRootElement(); + + if (root.getElementIndex(rowStartOffset) == root.getElementIndex(caretPosition)) + return true; + else + return false; + } + + /* + * Get the line number to be drawn. The empty string will be returned + * when a line of text has wrapped. + */ + protected String getTextLineNumber(int rowStartOffset) { + Element root = component.getDocument().getDefaultRootElement(); + int index = root.getElementIndex(rowStartOffset); + Element line = root.getElement(index); + + if (line.getStartOffset() == rowStartOffset) + return String.valueOf(index + 1); + else + return ""; + } + + /* + * Determine the X offset to properly align the line number when drawn + */ + private int getOffsetX(int availableWidth, int stringWidth) { + return (int) ((availableWidth - stringWidth) * digitAlignment); + } + + /* + * Determine the Y offset for the current row + */ + private int getOffsetY(int rowStartOffset, FontMetrics fontMetrics) + throws BadLocationException { + // Get the bounding rectangle of the row + + Rectangle r = component.modelToView(rowStartOffset); + int lineHeight = fontMetrics.getHeight(); + int y = r.y + r.height; + int descent = 0; + + // The text needs to be positioned above the bottom of the bounding + // rectangle based on the descent of the font(s) contained on the row. + + if (r.height == lineHeight) // default font is being used + { + descent = fontMetrics.getDescent(); + } else // We need to check all the attributes for font changes + { + if (fonts == null) + fonts = new HashMap<String, FontMetrics>(); + + Element root = component.getDocument().getDefaultRootElement(); + int index = root.getElementIndex(rowStartOffset); + Element line = root.getElement(index); + + for (int i = 0; i < line.getElementCount(); i++) { + Element child = line.getElement(i); + AttributeSet as = child.getAttributes(); + String fontFamily = (String) as.getAttribute(StyleConstants.FontFamily); + Integer fontSize = (Integer) as.getAttribute(StyleConstants.FontSize); + String key = fontFamily + fontSize; + + FontMetrics fm = fonts.get(key); + + if (fm == null) { + Font font = new Font(fontFamily, Font.PLAIN, fontSize); + fm = component.getFontMetrics(font); + fonts.put(key, fm); + } + + descent = Math.max(descent, fm.getDescent()); + } + } + + return y - descent; + } + + // + // Implement CaretListener interface + // + @Override + public void caretUpdate(CaretEvent e) { + // Get the line the caret is positioned on + + int caretPosition = component.getCaretPosition(); + Element root = component.getDocument().getDefaultRootElement(); + int currentLine = root.getElementIndex(caretPosition); + + // Need to repaint so the correct line number can be highlighted + + if (lastLine != currentLine) { + repaint(); + lastLine = currentLine; + } + } + + // + // Implement DocumentListener interface + // + @Override + public void changedUpdate(DocumentEvent e) { + documentChanged(); + } + + @Override + public void insertUpdate(DocumentEvent e) { + documentChanged(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + documentChanged(); + } + + /* + * A document change may affect the number of displayed lines of text. + * Therefore the lines numbers will also change. + */ + private void documentChanged() { + // View of the component has not been updated at the time + // the DocumentEvent is fired + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + try { + int endPos = component.getDocument().getLength(); + Rectangle rect = component.modelToView(endPos); + + if (rect != null && rect.y != lastHeight) { + setPreferredWidth(); + repaint(); + lastHeight = rect.y; + } + } catch (BadLocationException ex) { + /* nothing to do */ } + } + }); + } + + // + // Implement PropertyChangeListener interface + // + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getNewValue() instanceof Font) { + if (updateFont) { + Font newFont = (Font) evt.getNewValue(); + setFont(newFont); + lastDigits = 0; + setPreferredWidth(); + } else { + repaint(); + } + } + } +} diff --git a/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/viewer/ViewerApplication.java b/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/viewer/ViewerApplication.java new file mode 100644 index 00000000000..410cb6110a5 --- /dev/null +++ b/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/viewer/ViewerApplication.java @@ -0,0 +1,341 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.batch.protocol.viewer; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.sql.Date; +import java.text.SimpleDateFormat; +import java.util.Scanner; +import javax.swing.JEditorPane; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTabbedPane; +import javax.swing.JTree; +import javax.swing.UIManager; +import javax.swing.UIManager.LookAndFeelInfo; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeSelectionModel; +import org.sonar.batch.protocol.output.BatchReport; +import org.sonar.batch.protocol.output.BatchReport.Component; +import org.sonar.batch.protocol.output.BatchReport.Metadata; +import org.sonar.batch.protocol.output.BatchReportReader; +import org.sonar.batch.protocol.output.FileStructure.Domain; +import org.sonar.core.util.CloseableIterator; + +public class ViewerApplication { + + private JFrame frame; + private BatchReportReader reader; + private Metadata metadata; + private SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); + private JTree componentTree; + private JSplitPane splitPane; + private JTabbedPane tabbedPane; + private JScrollPane treeScrollPane; + private JScrollPane componentDetailsTab; + private JScrollPane highlightingTab; + private JEditorPane componentEditor; + private JEditorPane highlightingEditor; + private JScrollPane sourceTab; + private JEditorPane sourceEditor; + private JScrollPane coverageTab; + private JEditorPane coverageEditor; + private TextLineNumber textLineNumber; + + /** + * Launch the application. + */ + public static void main(String[] args) { + EventQueue.invokeLater(new Runnable() { + public void run() { + try { + ViewerApplication window = new ViewerApplication(); + window.frame.setVisible(true); + + window.loadReport(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + }); + } + + private void loadReport() { + final JFileChooser fc = new JFileChooser(); + fc.setDialogTitle("Choose batch report directory"); + fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + fc.setApproveButtonText("Open batch report"); + int returnVal = fc.showOpenDialog(frame); + if (returnVal == JFileChooser.APPROVE_OPTION) { + File file = fc.getSelectedFile(); + try { + loadReport(file); + } catch (Exception e) { + JOptionPane.showMessageDialog(frame, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); + exit(); + } + } else { + exit(); + } + + } + + private void exit() { + frame.setVisible(false); + frame.dispose(); + } + + private void loadReport(File file) { + reader = new BatchReportReader(file); + metadata = reader.readMetadata(); + updateTitle(); + loadComponents(); + } + + private void loadComponents() { + int rootComponentRef = metadata.getRootComponentRef(); + Component component = reader.readComponent(rootComponentRef); + DefaultMutableTreeNode project = createNode(component); + loadChildren(component, project); + getComponentTree().setModel(new DefaultTreeModel(project)); + } + + private DefaultMutableTreeNode createNode(Component component) { + DefaultMutableTreeNode node = new DefaultMutableTreeNode(component) { + @Override + public String toString() { + return getNodeName((Component) getUserObject()); + } + }; + return node; + } + + private String getNodeName(Component component) { + switch (component.getType()) { + case PROJECT: + case MODULE: + return component.getName(); + case DIRECTORY: + case FILE: + return component.getPath(); + default: + throw new IllegalArgumentException("Unknow component type: " + component.getType()); + } + } + + private void loadChildren(Component parentComponent, DefaultMutableTreeNode parentNode) { + for (int ref : parentComponent.getChildRefList()) { + Component child = reader.readComponent(ref); + DefaultMutableTreeNode childNode = createNode(child); + parentNode.add(childNode); + loadChildren(child, childNode); + } + + } + + private void updateTitle() { + frame.setTitle(metadata.getProjectKey() + (metadata.hasBranch() ? (" (" + metadata.getBranch() + ")") : "") + " " + sdf.format(new Date(metadata.getAnalysisDate()))); + } + + private void updateDetails(Component component) { + componentEditor.setText(component.toString()); + updateHighlighting(component); + updateSource(component); + updateCoverage(component); + } + + private void updateCoverage(Component component) { + coverageEditor.setText(""); + if (reader.hasCoverage(component.getRef())) { + try (CloseableIterator<BatchReport.Coverage> it = reader.readComponentCoverage(component.getRef())) { + while (it.hasNext()) { + BatchReport.Coverage coverage = it.next(); + coverageEditor.getDocument().insertString(coverageEditor.getDocument().getEndPosition().getOffset(), coverage.toString() + "\n", null); + } + } catch (Exception e) { + throw new IllegalStateException("Can't read code coverage for " + getNodeName(component), e); + } + } + } + + private void updateSource(Component component) { + File sourceFile = reader.getFileStructure().fileFor(Domain.SOURCE, component.getRef()); + if (sourceFile.exists()) { + try (Scanner s = new Scanner(sourceFile, StandardCharsets.UTF_8.name()).useDelimiter("\\Z")) { + sourceEditor.setText(s.next()); + } catch (IOException ex) { + StringWriter errors = new StringWriter(); + ex.printStackTrace(new PrintWriter(errors)); + sourceEditor.setText(errors.toString()); + } + } else { + sourceEditor.setText(""); + } + } + + private void updateHighlighting(Component component) { + highlightingEditor.setText(""); + if (reader.hasSyntaxHighlighting(component.getRef())) { + try (CloseableIterator<BatchReport.SyntaxHighlighting> it = reader.readComponentSyntaxHighlighting(component.getRef())) { + while (it.hasNext()) { + BatchReport.SyntaxHighlighting rule = it.next(); + highlightingEditor.getDocument().insertString(highlightingEditor.getDocument().getEndPosition().getOffset(), rule.toString() + "\n", null); + } + } catch (Exception e) { + throw new IllegalStateException("Can't read syntax highlighting for " + getNodeName(component), e); + } + } + } + + /** + * Create the application. + */ + public ViewerApplication() { + initialize(); + } + + /** + * Initialize the contents of the frame. + */ + private void initialize() { + try { + for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) { + if ("Nimbus".equals(info.getName())) { + UIManager.setLookAndFeel(info.getClassName()); + break; + } + } + } catch (Exception e) { + // If Nimbus is not available, you can set the GUI to another look and feel. + } + frame = new JFrame(); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + splitPane = new JSplitPane(); + frame.getContentPane().add(splitPane, BorderLayout.CENTER); + + tabbedPane = new JTabbedPane(JTabbedPane.TOP); + tabbedPane.setPreferredSize(new Dimension(500, 7)); + splitPane.setRightComponent(tabbedPane); + + componentDetailsTab = new JScrollPane(); + tabbedPane.addTab("Component details", null, componentDetailsTab, null); + + componentEditor = new JEditorPane(); + componentDetailsTab.setViewportView(componentEditor); + + sourceTab = new JScrollPane(); + tabbedPane.addTab("Source", null, sourceTab, null); + + sourceEditor = createSourceEditor(); + sourceEditor.setEditable(false); + sourceTab.setViewportView(sourceEditor); + + textLineNumber = createTextLineNumber(); + sourceTab.setRowHeaderView(textLineNumber); + + highlightingTab = new JScrollPane(); + tabbedPane.addTab("Highlighting", null, highlightingTab, null); + + highlightingEditor = new JEditorPane(); + highlightingTab.setViewportView(highlightingEditor); + + coverageTab = new JScrollPane(); + tabbedPane.addTab("Coverage", null, coverageTab, null); + + coverageEditor = new JEditorPane(); + coverageTab.setViewportView(coverageEditor); + + treeScrollPane = new JScrollPane(); + treeScrollPane.setPreferredSize(new Dimension(200, 400)); + splitPane.setLeftComponent(treeScrollPane); + + componentTree = new JTree(); + componentTree.setModel(new DefaultTreeModel( + new DefaultMutableTreeNode("empty") { + { + } + })); + treeScrollPane.setViewportView(componentTree); + componentTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + componentTree.addTreeSelectionListener(new TreeSelectionListener() { + public void valueChanged(TreeSelectionEvent e) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode) componentTree.getLastSelectedPathComponent(); + + if (node == null) { + // Nothing is selected. + return; + } + + updateDetails((Component) node.getUserObject()); + + } + + }); + frame.pack(); + } + + public JTree getComponentTree() { + return componentTree; + } + + /** + * @wbp.factory + */ + public static JPanel createComponentPanel() { + JPanel panel = new JPanel(); + return panel; + } + + protected JEditorPane getComponentEditor() { + return componentEditor; + } + + /** + * @wbp.factory + */ + public static JEditorPane createSourceEditor() { + JEditorPane editorPane = new JEditorPane(); + return editorPane; + } + + /** + * @wbp.factory + */ + public TextLineNumber createTextLineNumber() { + TextLineNumber textLineNumber = new TextLineNumber(sourceEditor); + return textLineNumber; + } +} |