How to show multiple carets in a JTextArea or JTextPane - java

I want to use a JTextArea or JTextPane as a code change player, and the code changes and caret movements are recorded in a text file. But the problem is, it's recorded from an editor which supports multi selection, so there are more than one caret positions at one time.
Is it possible to show multiple carets in JTextArea or JTextPane?
I tried to use JTextPane and render the code as HTML, and inserted some <span class='caret'>|</span> into the code to represent the carets, it works but the fake caret takes space, so the normal characters are not fixed on screen when caret changes.

Like this?
import javax.swing.*;
import javax.swing.plaf.TextUI;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultCaret;
import java.awt.*;
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
JFrame fr=new JFrame("Multi caret test");
JTextArea ta=new JTextArea("Test test test", 20, 40);
MultiCaret c=new MultiCaret();
c.setBlinkRate(500);
c.setAdditionalDots(Arrays.asList(2,4,7));
ta.setCaret(c);
fr.add(ta);
fr.pack();
fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
fr.setLocationRelativeTo(null);
fr.setVisible(true);
}
}
class MultiCaret extends DefaultCaret {
private List<Integer> additionalDots;
public void setAdditionalDots(List<Integer> additionalDots) {
this.additionalDots = additionalDots;
}
public void paint(Graphics g) {
super.paint(g);
try {
TextUI mapper = getComponent().getUI();
for (Integer addDot : additionalDots) {
Rectangle r = mapper.modelToView(getComponent(), addDot, getDotBias());
if(isVisible()) {
g.setColor(getComponent().getCaretColor());
int paintWidth = 1;
r.x -= paintWidth >> 1;
g.fillRect(r.x, r.y, paintWidth, r.height);
}
else {
getComponent().repaint(r);
}
}
} catch (BadLocationException e) {
e.printStackTrace();
}
}
}

an editor which supports multi selection,
Maybe you should be using a Highlighter to highlight multiple areas of text selection:
import java.awt.*;
import javax.swing.*;
import javax.swing.text.*;
public class TextAndNewLinesTest extends JFrame
{
public TextAndNewLinesTest()
throws Exception
{
String text =
"one two three four five\r\n" +
"one two three four five\r\n" +
"one two three four five\r\n" +
"one two three four five\r\n" +
"one two three four five\r\n";
JTextPane textPane = new JTextPane();
textPane.setText(text);
JScrollPane scrollPane = new JScrollPane( textPane );
getContentPane().add( scrollPane );
Highlighter.HighlightPainter painter = new DefaultHighlighter.DefaultHighlightPainter( Color.cyan );
String search = "three";
int offset = 0;
int length = textPane.getDocument().getLength();
text = textPane.getDocument().getText(0, length);
while ((offset = text.indexOf(search, offset)) != -1)
{
try
{
textPane.getHighlighter().addHighlight(offset, offset + 5, painter); // background
offset += search.length();
}
catch(BadLocationException ble) {}
}
}
public static void main(String[] args)
throws Exception
{
JFrame.setDefaultLookAndFeelDecorated(true);
JFrame frame = new TextAndNewLinesTest();
frame.setTitle("Text and New Lines - Problem");
// frame.setTitle("Text and New Lines - Fixed");
frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
frame.setSize(400, 120);
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
}

Related

command line indicator in JTextArea

I've made a JTextArea where I input different commands and separate them by newline "\n", and if there is an error in one of the lines then I write it in a console output. Here I made a very simple, and not the best solution to make this line indication, but it's a bit buggy.
How I made it
I've defined a textArea where I can type different information/commands, and if one of the commands/lines is invalid I write it in the console just to display something for now. I basically count the lines by splitting the textArea rows up by "\n" and then count which line the error occurs in, and the left consoleLineNum is using the amount of rows in textArea, to then make a string containing all the numbers of rows+"\n".
But here my question is, is this a good enough way? If so, why/how can I make it more robust? Or how can I make this indication with line numbers, in the left? It has to increase each time the user makes a new line in the textArea.
private static void createAndShowGUI() {
//Create and set up the window.
JFrame frame = new JFrame("GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(width, height);
frame.setLayout(new BorderLayout(3,3));
/*----- Panels -----*/
JPanel panel1 = new JPanel();
JPanel panel2 = new JPanel();
JPanel panel3 = new JPanel();
//Add Components to this panel.
JScrollPane scrollPane = new JScrollPane(textArea);
JScrollPane scrollPaneOutput = new JScrollPane(consoleOutput);
textArea.setCaretPosition(textArea.getDocument().getLength());
consoleOutput.setEditable(false);
consoleLineNum.setEditable(false);
ButtonPanel_listener(buttonPanel);
textArea.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
consoleLineNum.setText("");
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= Integer.parseInt(String.valueOf(textArea.getText().split("\n").length)); i++) {
sb.append(i + " \n");
}
consoleLineNum.setText(sb.toString());
}
#Override
public void keyReleased(KeyEvent e) {
}
});
//Background
panel1.setBackground(Color.LIGHT_GRAY);
//Preferred size
panel1.setPreferredSize(new Dimension(100, 100));
scrollPane.setPreferredSize(new Dimension(200, 200));
panel2.setLayout(new BorderLayout());
panel2.add(scrollPane, BorderLayout.CENTER);
panel2.add(buttonPanel, BorderLayout.SOUTH);
panel2.add(consoleLineNum, BorderLayout.WEST);
consoleLineNum.setText(num);
panel3.setLayout(new BorderLayout());
panel3.add(drawCanvas, BorderLayout.CENTER);
panel1.setLayout(new BorderLayout());
panel1.add(scrollPaneOutput, BorderLayout.CENTER);
//textArea.addActionListener(e -> drawCanvas.drawCircle(250, 250, 200));
//Add contents to the window.
frame.add(panel2, BorderLayout.WEST);
frame.add(panel3, BorderLayout.CENTER);
frame.add(panel1, BorderLayout.SOUTH);
//Display the window.
frame.pack();
frame.setVisible(true);
}
Difference between your expected and actual results
When I hit newline/Enter, it doesn't show the number right away, only when I start typing.
Or
Here I want it to match where the user is, so if the user hit enter, and go to the next line, then the number matches and is shown right away.
If I delete all lines, except some, it still shows the numbers
Here I want it to wipe all the numbers and update it to match the amount of data in textArea.
Tried this, and it works almost as expected. The only problem is that when I delete lines, it's one behind.
#Override
public void keyPressed(KeyEvent e) {
numI = textArea.getLineCount();
if(e.getKeyCode() == KeyEvent.VK_BACK_SPACE && numI > 0) {
numI = numI - 1;
}
if(e.getKeyCode() == KeyEvent.VK_BACK_SPACE || e.getKeyCode() == KeyEvent.VK_ENTER){
StringBuilder sb = new StringBuilder();
for (int i = 0; i <= numI; i++) {
sb.append(i+1 + " \n");
}
consoleLineNum.setText(sb.toString());
}
}
Use a DocumentListener to listen for changes in the text of the JTextArea. The API of JTextArea already provides a method that tells you how many lines it contains, namely getLineCount(). After placing the JTextArea in a JScrollPane, set a JList as the row header for the JScrollPane.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.swing.DefaultListModel;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JViewport;
import javax.swing.WindowConstants;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
public class CountLns implements DocumentListener, Runnable {
private JFrame frame;
private JList<Integer> lineNumbersList;
private JTextArea textArea;
public void changedUpdate(DocumentEvent docEvent) {
// Never called.
}
public void insertUpdate(DocumentEvent docEvent) {
handleDocEvent();
}
public void removeUpdate(DocumentEvent docEvent) {
handleDocEvent();
}
#Override
public void run() {
showGui();
}
private JScrollPane createTextArea() {
textArea = new JTextArea(20, 50);
textArea.getDocument().addDocumentListener(this);
JScrollPane scrollPane = new JScrollPane(textArea);
lineNumbersList = new JList<>(new Integer[]{1});
lineNumbersList.setBackground(Color.cyan);
lineNumbersList.setFont(textArea.getFont());
lineNumbersList.setFixedCellHeight(16);
JViewport rowHeader = new JViewport();
rowHeader.setView(lineNumbersList);
scrollPane.setRowHeader(rowHeader);
return scrollPane;
}
private void handleDocEvent() {
DefaultListModel<Integer> model = new DefaultListModel<>();
List<Integer> lineNumbers = IntStream.range(0, textArea.getLineCount())
.boxed()
.map(i -> i + 1)
.collect(Collectors.toList());
model.addAll(lineNumbers);
lineNumbersList.setModel(model);
}
private void showGui() {
frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(createTextArea(), BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
textArea.requestFocusInWindow();
frame.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new CountLns());
}
}
Whenever the contents of the JTextArea change, the DocumentListener counts the lines in the JTextArea and sets the JList model to contain exactly that number of elements. So if the JTextArea has 12 lines, the JList will contain all the numbers from 1 to 12.
Try doing it like this:
yourTextArea.addKeyListener(new KeyAdapter() {
#Override
public void keyTyped(KeyEvent e) {
// Returns true every time the user presses either backspace or enter.
if (e.getKeyChar() == '\n' || e.getKeyChar() == '\b') {
int lines;
// Checks if the text ends with a newline, if so,
// it adds 1 to the line count, so that would make your line
// appear even if the user just started a new line.
if (yourTextArea.getText().endsWith("\n")) {
lines = yourTextArea.getText().split("\n").length + 1;
} else {
lines = yourTextArea.getText().split("\n").length;
}
// Removes previous count.
linePanel.setText("");
// Appends a new line to the area for every line.
for (int i = 0; i < lines; i++) {
linePanel.append((i + 1) + "\n");
}
}
}
});
I did not write in Java for 1 year so sorry, if I messed up syntax
First of all, why won't you implement line wrap and line count increment by pressing Enter? It looks much more simple to me
#Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_ENTER) {
textArea.append("\n");
Integer lineCount = Integer.parseInt(consoleLineNum.getText());
Integer newLineNumber = new Integer(lineCount.intValue() + 1)
consoleLineNum.append("\n" + newLineNumber.toString())
}
}
However you still will not get actual number of lines, if some of them will be deleted. So you can also add trickier logic to your keyPressed() method
if(e.getKeyCode() == KeyEvent.VK_BACK_SPACE) {
// Well I actually googled this line for counting occurences
Integer linesCountActual = textArea.getText().split("\n", -1).length - 1);
Integer linesCountLeft = consoleLineNum.getText().split("\n", -1).length - 1);
if (linesCountActual > linesCountLeft) {
String oldText = consoleLineNum.getText();
String newText = oldText.substring(0, oldText.length() - 2);
consoleLineNum.setText(newText);
}
}

JLabel.setText() relocates all labels in mousewheelevent

EDIT: leaving short code which shows issue:
Then label.setText("") is called and it has to change the text, i.e. scaler overcame 50 barrier the label dissapears for the mousewheelevent -> re-emerges on next event. Also a problem of the label not being present on the Load-up.
In actual program labels stack together in 1 spot if any of them has .setText() changing value or .setVisible changing (These two methods I need to be able to use without affecting labels positioning)
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.OverlayLayout;
public class brokenLabel extends JFrame
{
private JLayeredPane layerPane;
private JPanel breakingLabelPanel = new JPanel();
private JLabel label;
private int scaler = 50;
private int xstart = 200;
private int ystart = 200;
public static void main(String[] args)
{
#SuppressWarnings("unused")
brokenLabel program = new brokenLabel();
}
public brokenLabel()
{
// Frame setup used on the program
final JFrame frame = new JFrame("This is a slideshow");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
layerPane = new JLayeredPane();
frame.setContentPane(layerPane);
// Panel used (it's similar to what's added on the actual program)
breakingLabelPanel.setOpaque(true);
breakingLabelPanel.setLayout(new OverlayLayout(breakingLabelPanel));
breakingLabelPanel.setVisible(true);
breakingLabelPanel.setSize(getMaximumSize());
breakingLabelPanel.addMouseWheelListener(new MouseWheelListener(){
#Override
public void mouseWheelMoved(MouseWheelEvent arg0)
{
if (scaler > 5 || arg0.getWheelRotation() < 0)
scaler = scaler - 5*arg0.getWheelRotation();
setTexts();
}
});
//The testing label
label = new JLabel(new String("1"));
label.setLocation(xstart , ystart);
breakingLabelPanel.add(label);
frame.add(breakingLabelPanel,layerPane);
frame.setVisible(true);
//revalidate and repaint don't help make label visible on load-up
frame.revalidate();
frame.repaint();
}
public void setTexts(){
label.setLocation(xstart + scaler,ystart + 25);
if(scaler < 50)
{
label.setText("1");
}
else
{
label.setText("2");
}
}
}

Opening a text file in a frame using swing components

I want to open a text file in a frame using swing components, preferably with highlighting facility. I get the name of the text file in a text filed in the first frame and want to open the text file in the second frame.My code is
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class FirstGUI extends JFrame {
private JLabel label;
private JTextField textfield;
private JButton button;
public FirstGUI() {
setLayout(new FlowLayout());
label = new JLabel("Enter the file path:");
add(label);
textfield = new JTextField();
add(textfield);
button = new JButton("Open");
add(button);
AnyClass ob = new AnyClass();
button.addActionListener(ob);
}
public class AnyClass implements ActionListener {
public void actionPerformed(ActionEvent obj) {
//SecondGUI s =new SecondGUI();
//s.setVisible(true);
}
}
public static void main(String[] args) {
FirstGUI obj= new FirstGUI();
obj.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
obj.setSize(600,600);
obj.setLocation(100,100);
obj.setVisible(true);
}
}
What swing component should I use in my second frame to open a text file in it ? If possible, please provide the outline of the code !!
Extending on mKorbel and Dans answer:
Well you could use a JTextArea like so:
import java.awt.BorderLayout;
import java.awt.Color;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.text.*;
public class LineHighlightPainter {
String revisedText = "Hello, World! ";
String token = "Hello";
public static void main(String args[]) {
try {
SwingUtilities.invokeAndWait(new Runnable() {
#Override
public void run() {
new LineHighlightPainter().createAndShowGUI();
}
});
} catch (InterruptedException | InvocationTargetException ex) {
ex.printStackTrace();
}
}
public void createAndShowGUI() {
JFrame frame = new JFrame("LineHighlightPainter demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JTextArea area = new JTextArea(9, 45);
area.setLineWrap(true);
area.setWrapStyleWord(true);
area.setText(revisedText);
// Highlighting part of the text in the instance of JTextArea
// based on token.
highlight(area, token);
frame.getContentPane().add(new JScrollPane(area), BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
}
// Creates highlights around all occurrences of pattern in textComp
public void highlight(JTextComponent textComp, String pattern) {
// First remove all old highlights
removeHighlights(textComp);
try {
Highlighter hilite = textComp.getHighlighter();
Document doc = textComp.getDocument();
String text = doc.getText(0, doc.getLength());
int pos = 0;
// Search for pattern
while ((pos = text.indexOf(pattern, pos)) >= 0) {
// Create highlighter using private painter and apply around pattern
hilite.addHighlight(pos, pos + pattern.length(), myHighlightPainter);
pos += pattern.length();
}
} catch (BadLocationException e) {
e.printStackTrace();
}
}
// Removes only our private highlights
public void removeHighlights(JTextComponent textComp) {
Highlighter hilite = textComp.getHighlighter();
Highlighter.Highlight[] hilites = hilite.getHighlights();
for (int i = 0; i < hilites.length; i++) {
if (hilites[i].getPainter() instanceof MyHighlightPainter) {
hilite.removeHighlight(hilites[i]);
}
}
}
// An instance of the private subclass of the default highlight painter
Highlighter.HighlightPainter myHighlightPainter = new MyHighlightPainter(Color.red);
// A private subclass of the default highlight painter
class MyHighlightPainter
extends DefaultHighlighter.DefaultHighlightPainter {
public MyHighlightPainter(Color color) {
super(color);
}
}
}
Or alternatively use a JTextPane and text can be highlighted by:
1) Changing any style attributes of arbitrary text parts on the document level, something like:
SimpleAttributeSet sas = new SimpleAttributeSet();
StyleConstants.setForeground(sas, Color.YELLOW);
doc.setCharacterAttributes(start, length, sas, false);
2) Highlight via a Highlighter on the textPane level:
DefaultHighlighter.DefaultHighlightPainter highlightPainter = new DefaultHighlighter.DefaultHighlightPainter(Color.YELLOW);
textPane.getHighlighter().addHighlight(startPos, endPos,highlightPainter);
References:
Highlighting Text in java
JTextPane highlight text
The simplest choice would be a JTextArea.
Another better choice is a JEditorPane.
You can take a look at this text components tutorial for understanding them better and choosing the best you need.
have look at
JFileChooser
JTextComponent#read()
JtextArea text
fileInputStream myFIS;
objectInputStream myOIS(myFIS);
Data = myOIS.read();
text.setText(Data);
that should give you some kind of an idea where to go. Don't forget to set the file input stream up with a file location so it knows what file to open. Then the ObjectInputStream will take the data and save the information into a field called Data. Then set the textArea to use Data as the information to "Set" the textArea to display.
Note: ObjectInputStream is not the only input stream available to use. you will have to use the input stream that correlates to your file.

JScrollPane scroll at last added line

I have JTextArea text and JScrollPane pane=new JScrollPane(text), I put pane.setAutoScrolls(true). How to get that when I append some text to my component text that pane scrolls at the end ( last line ) ?
follows link from this thread ScrollPane scroll to bottom problem
Best (and up-to-date, as far as I can tell) explanation of how caret is moved, by Rob Camick:
http://tips4java.wordpress.com/2008/10/22/text-area-scrolling/
Is it possible, that you are not on the EDT?
If the append does not happen on the EDT, the position of the JTextArea does not update.
Short, runnable example to show this behaviour:
import java.awt.TextArea;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
public class Sample {
public static void main(String[] args) {
/*
* Not on EDT
*/
showAndFillTextArea("Not on EDT", 0, 0);
/*
* On EDT
*/
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
showAndFillTextArea("On EDT", 400, 0);
}
});
}
private static void showAndFillTextArea(String title, int x, int y) {
JFrame frame = new JFrame(title);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JTextArea textArea = new JTextArea(20, 20);
JScrollPane scrollPane = new JScrollPane(textArea);
frame.getContentPane().add(scrollPane);
frame.pack();
frame.setLocation(x, y);
frame.setVisible(true);
for(int i = 0; i < 50; i++) {
textArea.append("Line" + i + "\n");
}
}
}

How to make a specific row visible of a textArea in Java

My JTextArea contains thousands of lines but not all of them are visible at a time. I want to programmatically scroll to a specific row of the textArea so that the line is visible. I found that scrollPane has a method scrollRectToVisible but I am not successful with that. Can anyone suggest me how to accomplish the goal. A workable code snippet will be really helpful for me. Thanks.
scrollRectToVisible(...) should work. Make sure you invoke scrollRectToVisible(...) on the text area and not the scrollpane. If that doesn't work then I would guess you are not getting the proper Rectangle to scroll to. Post your SSCCE that demonstrates the problem.
Another approach is to use the gotoStartOfLine(...) method of the Text Utilities. You can also use the centerLineInScrollPane(...) method if you wish.
I guess you've answered this already. I was creating my SSCCE during this time, so I'll post it for others' benefit if not yours.
import java.awt.BorderLayout;
import java.awt.Rectangle;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.text.BadLocationException;
public class TestScrollRectToVisible extends JPanel {
private static final int MAX_LOOP = 10000;
private DefaultListModel listModel = new DefaultListModel();
private JTextArea textarea = new JTextArea(20, 30);
private JList jList = new JList(listModel);
JScrollPane textareaScrollPane = new JScrollPane(textarea);
public TestScrollRectToVisible() {
jList.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
if (!e.getValueIsAdjusting()) {
String text = jList.getSelectedValue().toString();
text += ": ";
String docText = textarea.getText();
int index = docText.indexOf(text);
if (index < 0) {
return;
}
try {
Rectangle rect = textarea.modelToView(index);
textarea.scrollRectToVisible(rect);
} catch (BadLocationException e1) {
e1.printStackTrace();
}
}
}
});
jList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
StringBuilder strBuilder = new StringBuilder();
for (int i = 0; i < MAX_LOOP; i++) {
String text = String.valueOf(i);
listModel.addElement(text);
strBuilder.append(text + ": abcdefghijklmnopqrstuvwxyz" + "\n");
}
textarea.setText(strBuilder.toString());
setLayout(new BorderLayout());
add(textareaScrollPane, BorderLayout.CENTER);
add(new JScrollPane(jList), BorderLayout.EAST);
}
private static void createAndShowUI() {
JFrame frame = new JFrame("TestScrollRectToVisible");
frame.getContentPane().add(new TestScrollRectToVisible());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
createAndShowUI();
}
});
}
}

Categories

Resources