Add code samples, disable editing when running code, a little renaming
This commit is contained in:
parent
67d4b6d082
commit
d4057afde7
@ -45,6 +45,29 @@
|
|||||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="openMenuActionPerformed"/>
|
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="openMenuActionPerformed"/>
|
||||||
</Events>
|
</Events>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<Menu class="javax.swing.JMenu" name="openSampleBtn">
|
||||||
|
<Properties>
|
||||||
|
<Property name="text" type="java.lang.String" value="Open Code Sample"/>
|
||||||
|
</Properties>
|
||||||
|
<SubComponents>
|
||||||
|
<MenuItem class="javax.swing.JMenuItem" name="sampleHelloWorld">
|
||||||
|
<Properties>
|
||||||
|
<Property name="text" type="java.lang.String" value="helloworld"/>
|
||||||
|
</Properties>
|
||||||
|
<Events>
|
||||||
|
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="sampleHelloWorldActionPerformed"/>
|
||||||
|
</Events>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem class="javax.swing.JMenuItem" name="sampleGraph">
|
||||||
|
<Properties>
|
||||||
|
<Property name="text" type="java.lang.String" value="graph"/>
|
||||||
|
</Properties>
|
||||||
|
<Events>
|
||||||
|
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="sampleGraphActionPerformed"/>
|
||||||
|
</Events>
|
||||||
|
</MenuItem>
|
||||||
|
</SubComponents>
|
||||||
|
</Menu>
|
||||||
<MenuItem class="javax.swing.JMenuItem" name="saveMenu">
|
<MenuItem class="javax.swing.JMenuItem" name="saveMenu">
|
||||||
<Properties>
|
<Properties>
|
||||||
<Property name="accelerator" type="javax.swing.KeyStroke" editor="org.netbeans.modules.form.editors.KeyStrokeEditor">
|
<Property name="accelerator" type="javax.swing.KeyStroke" editor="org.netbeans.modules.form.editors.KeyStrokeEditor">
|
||||||
|
@ -28,17 +28,22 @@
|
|||||||
package net.apocalypselabs.symat;
|
package net.apocalypselabs.symat;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
|
import java.awt.Component;
|
||||||
import java.awt.Font;
|
import java.awt.Font;
|
||||||
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import javax.swing.JComponent;
|
||||||
import javax.swing.JFileChooser;
|
import javax.swing.JFileChooser;
|
||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.filechooser.FileFilter;
|
import javax.swing.filechooser.FileFilter;
|
||||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||||
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
|
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
|
||||||
@ -148,6 +153,9 @@ public class CodeEditor extends javax.swing.JInternalFrame {
|
|||||||
jMenuBar1 = new javax.swing.JMenuBar();
|
jMenuBar1 = new javax.swing.JMenuBar();
|
||||||
fileMenu = new javax.swing.JMenu();
|
fileMenu = new javax.swing.JMenu();
|
||||||
openMenu = new javax.swing.JMenuItem();
|
openMenu = new javax.swing.JMenuItem();
|
||||||
|
openSampleBtn = new javax.swing.JMenu();
|
||||||
|
sampleHelloWorld = new javax.swing.JMenuItem();
|
||||||
|
sampleGraph = new javax.swing.JMenuItem();
|
||||||
saveMenu = new javax.swing.JMenuItem();
|
saveMenu = new javax.swing.JMenuItem();
|
||||||
saveAsMenu = new javax.swing.JMenuItem();
|
saveAsMenu = new javax.swing.JMenuItem();
|
||||||
exportMenu = new javax.swing.JMenuItem();
|
exportMenu = new javax.swing.JMenuItem();
|
||||||
@ -243,6 +251,26 @@ public class CodeEditor extends javax.swing.JInternalFrame {
|
|||||||
});
|
});
|
||||||
fileMenu.add(openMenu);
|
fileMenu.add(openMenu);
|
||||||
|
|
||||||
|
openSampleBtn.setText("Open Code Sample");
|
||||||
|
|
||||||
|
sampleHelloWorld.setText("helloworld");
|
||||||
|
sampleHelloWorld.addActionListener(new java.awt.event.ActionListener() {
|
||||||
|
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
|
sampleHelloWorldActionPerformed(evt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
openSampleBtn.add(sampleHelloWorld);
|
||||||
|
|
||||||
|
sampleGraph.setText("graph");
|
||||||
|
sampleGraph.addActionListener(new java.awt.event.ActionListener() {
|
||||||
|
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
|
sampleGraphActionPerformed(evt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
openSampleBtn.add(sampleGraph);
|
||||||
|
|
||||||
|
fileMenu.add(openSampleBtn);
|
||||||
|
|
||||||
saveMenu.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S, java.awt.event.InputEvent.CTRL_MASK));
|
saveMenu.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S, java.awt.event.InputEvent.CTRL_MASK));
|
||||||
saveMenu.setText("Save...");
|
saveMenu.setText("Save...");
|
||||||
saveMenu.addActionListener(new java.awt.event.ActionListener() {
|
saveMenu.addActionListener(new java.awt.event.ActionListener() {
|
||||||
@ -354,28 +382,43 @@ public class CodeEditor extends javax.swing.JInternalFrame {
|
|||||||
codeBox.setCaretPosition(0);
|
codeBox.setCaretPosition(0);
|
||||||
}//GEN-LAST:event_openMenuActionPerformed
|
}//GEN-LAST:event_openMenuActionPerformed
|
||||||
|
|
||||||
public void openFileFromString(String file) {
|
/**
|
||||||
|
* Open a file given its path as a String.
|
||||||
|
*
|
||||||
|
* @param file The path to the file.
|
||||||
|
*/
|
||||||
|
public void openFileFromName(String file) {
|
||||||
try {
|
try {
|
||||||
File f = new File(file);
|
File f = new File(file);
|
||||||
codeBox.setText(readFile(f.toString(), StandardCharsets.UTF_8));
|
openString(readFile(f.toString(), StandardCharsets.UTF_8), f.getName(), true);
|
||||||
isSaved = true;
|
|
||||||
lastSaved = codeBox.getText();
|
|
||||||
setTitle("Editor - " + f.getName());
|
|
||||||
if (file.matches(".*\\.(js|mls|symt|syjs)")) {
|
|
||||||
javascriptOption.setSelected(true);
|
|
||||||
pythonOption.setSelected(false);
|
|
||||||
codeBox.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT);
|
|
||||||
} else if (file.matches(".*\\.(sypy|py)")) {
|
|
||||||
javascriptOption.setSelected(false);
|
|
||||||
pythonOption.setSelected(true);
|
|
||||||
codeBox.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_PYTHON);
|
|
||||||
}
|
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
JOptionPane.showInternalMessageDialog(this,
|
JOptionPane.showInternalMessageDialog(this,
|
||||||
"Error: Cannot load file: " + ex.getMessage());
|
"Error: Cannot load file: " + ex.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a file.
|
||||||
|
*
|
||||||
|
* @param data The file contents
|
||||||
|
* @param file Name of the file (ex. "foobar.syjs")
|
||||||
|
*/
|
||||||
|
private void openString(String data, String file, boolean saved) {
|
||||||
|
codeBox.setText(data);
|
||||||
|
isSaved = saved;
|
||||||
|
lastSaved = codeBox.getText();
|
||||||
|
setTitle("Editor - " + file);
|
||||||
|
if (file.matches(".*\\.(js|mls|symt|syjs)")) {
|
||||||
|
javascriptOption.setSelected(true);
|
||||||
|
pythonOption.setSelected(false);
|
||||||
|
codeBox.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT);
|
||||||
|
} else if (file.matches(".*\\.(sypy|py)")) {
|
||||||
|
javascriptOption.setSelected(false);
|
||||||
|
pythonOption.setSelected(true);
|
||||||
|
codeBox.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_PYTHON);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void saveMenuActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveMenuActionPerformed
|
private void saveMenuActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveMenuActionPerformed
|
||||||
if (!isSaved) {
|
if (!isSaved) {
|
||||||
int r = fc.showSaveDialog(this);
|
int r = fc.showSaveDialog(this);
|
||||||
@ -418,6 +461,9 @@ public class CodeEditor extends javax.swing.JInternalFrame {
|
|||||||
}
|
}
|
||||||
}//GEN-LAST:event_runCodeBtnActionPerformed
|
}//GEN-LAST:event_runCodeBtnActionPerformed
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This thread runs the code.
|
||||||
|
*/
|
||||||
private class RunThread extends Thread {
|
private class RunThread extends Thread {
|
||||||
|
|
||||||
String lang = "";
|
String lang = "";
|
||||||
@ -428,7 +474,33 @@ public class CodeEditor extends javax.swing.JInternalFrame {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
setRunning(true);
|
||||||
execCode(lang);
|
execCode(lang);
|
||||||
|
setRunning(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRunning(boolean isRunning) {
|
||||||
|
final boolean running = isRunning;
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (running) {
|
||||||
|
runMenu.setText("Running...");
|
||||||
|
codeBox.setEnabled(false);
|
||||||
|
for (Component mu : jMenuBar1.getComponents()) {
|
||||||
|
mu.setEnabled(false);
|
||||||
|
}
|
||||||
|
jMenuBar1.setEnabled(false);
|
||||||
|
} else {
|
||||||
|
runMenu.setText("Run");
|
||||||
|
codeBox.setEnabled(true);
|
||||||
|
for (Component mu : jMenuBar1.getComponents()) {
|
||||||
|
mu.setEnabled(true);
|
||||||
|
}
|
||||||
|
jMenuBar1.setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -472,6 +544,46 @@ public class CodeEditor extends javax.swing.JInternalFrame {
|
|||||||
formMouseClicked(evt);
|
formMouseClicked(evt);
|
||||||
}//GEN-LAST:event_outputBoxMouseClicked
|
}//GEN-LAST:event_outputBoxMouseClicked
|
||||||
|
|
||||||
|
private void sampleHelloWorldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sampleHelloWorldActionPerformed
|
||||||
|
openSample("helloworld");
|
||||||
|
}//GEN-LAST:event_sampleHelloWorldActionPerformed
|
||||||
|
|
||||||
|
private void sampleGraphActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sampleGraphActionPerformed
|
||||||
|
openSample("graph");
|
||||||
|
}//GEN-LAST:event_sampleGraphActionPerformed
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a sample code file with the given name.<p>
|
||||||
|
* Uses the current language.
|
||||||
|
*
|
||||||
|
* @param name Name of file in codesamples package without extension
|
||||||
|
*/
|
||||||
|
private void openSample(String name) {
|
||||||
|
String ext = "js";
|
||||||
|
if (!javascriptOption.isSelected()) {
|
||||||
|
ext = "py";
|
||||||
|
}
|
||||||
|
String text = "";
|
||||||
|
try {
|
||||||
|
BufferedReader reader = new BufferedReader(
|
||||||
|
new InputStreamReader(
|
||||||
|
CodeEditor.class
|
||||||
|
.getResourceAsStream("codesamples/" + name + "." + ext)));
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
text += line + "\n";
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
text = "Error: Could not open embedded sample file.";
|
||||||
|
if (ext.equals("js")) {
|
||||||
|
text = "/* " + text + " */";
|
||||||
|
} else {
|
||||||
|
text = "# " + text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
openString(text, name + "." + ext, false);
|
||||||
|
}
|
||||||
|
|
||||||
private void saveFile(String content, String path)
|
private void saveFile(String content, String path)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
try (PrintStream out = new PrintStream(new FileOutputStream(path))) {
|
try (PrintStream out = new PrintStream(new FileOutputStream(path))) {
|
||||||
@ -525,11 +637,14 @@ public class CodeEditor extends javax.swing.JInternalFrame {
|
|||||||
private javax.swing.JRadioButtonMenuItem javascriptOption;
|
private javax.swing.JRadioButtonMenuItem javascriptOption;
|
||||||
private javax.swing.ButtonGroup langBtnGroup;
|
private javax.swing.ButtonGroup langBtnGroup;
|
||||||
private javax.swing.JMenuItem openMenu;
|
private javax.swing.JMenuItem openMenu;
|
||||||
|
private javax.swing.JMenu openSampleBtn;
|
||||||
private javax.swing.JTextArea outputBox;
|
private javax.swing.JTextArea outputBox;
|
||||||
private javax.swing.JPanel outputPanel;
|
private javax.swing.JPanel outputPanel;
|
||||||
private javax.swing.JRadioButtonMenuItem pythonOption;
|
private javax.swing.JRadioButtonMenuItem pythonOption;
|
||||||
private javax.swing.JMenuItem runCodeBtn;
|
private javax.swing.JMenuItem runCodeBtn;
|
||||||
private javax.swing.JMenu runMenu;
|
private javax.swing.JMenu runMenu;
|
||||||
|
private javax.swing.JMenuItem sampleGraph;
|
||||||
|
private javax.swing.JMenuItem sampleHelloWorld;
|
||||||
private javax.swing.JMenuItem saveAsMenu;
|
private javax.swing.JMenuItem saveAsMenu;
|
||||||
private javax.swing.JMenuItem saveMenu;
|
private javax.swing.JMenuItem saveMenu;
|
||||||
// End of variables declaration//GEN-END:variables
|
// End of variables declaration//GEN-END:variables
|
||||||
|
@ -90,7 +90,7 @@ public class MainGUI extends javax.swing.JFrame {
|
|||||||
} else {
|
} else {
|
||||||
CodeEditor ed = new CodeEditor();
|
CodeEditor ed = new CodeEditor();
|
||||||
loadFrame(ed);
|
loadFrame(ed);
|
||||||
ed.openFileFromString(argfile);
|
ed.openFileFromName(argfile);
|
||||||
argfile = "";
|
argfile = "";
|
||||||
}
|
}
|
||||||
updateDisplay();
|
updateDisplay();
|
||||||
|
@ -1,455 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2014 Apocalypse Laboratories.
|
|
||||||
*
|
|
||||||
* This library 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 2.1 of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This library 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 library; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
||||||
* MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
package net.apocalypselabs.symat;
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user