I'm developing a small calculator widget that keeps a running log of calculations. It's supposed to scroll to the bottom of the log every time a new entry is added. This part seems to be working fine.
The problem is, when I press a calculator button that does not add to the log, the log pane always scrolls back to the top, and the scrollbar disappears. How can I keep it from doing this?
The code that adds to the log is:
private JTextPane logArea; //This is placed inside a JScrollPane
private void log(String m, SimpleAttributeSet a) {
int len = logArea.getDocument().getLength();
logArea.setEditable(true);
logArea.setCaretPosition(len);
logArea.setCharacterAttributes(a, false);
logArea.replaceSelection(m);
logArea.scrollRectToVisible(new Rectangle(0,logArea.getBounds(null).height,1,1));
logArea.setEditable(false);
}
The code that seems to be messing with the scroll is:
private void addDigit(char digit) {
if (clearDisplayBeforeDigit) {
clearNumDisplay();
}
if (numInDisplay.getText().length() < maxNumDigits) {
if (digit == '.') { //Point
if (!hasPoint) { //Only one point allowed
hasPoint = true;
String newText = numInDisplay.getText() + ".";
numInDisplay.setText(newText);
}
} else { //New digit
String newText = numInDisplay.getText() + digit;
numInDisplay.setText(newText);
}
}
}
The code you think is causing the problem doesn't even reference the logArea, so why would you think this causes the problem?
You don't need to use the scrollRectToVisible(...) method. The setCaretPosition(...) should do the trick. Although you should get the length of the document and invoke that method AFTER you update the document.
Check out Text Area Scrolling for more information.
Edit:
I also don't see any reason for changing the editability of the text area.
Related
I'm creating a text adventure and I need to completely disable the mouse cursor. Not just hide it, although I already know how to do that, but disable it completely so that you must use Alt-Tab or a built-in quit button to stop. The main reason for this is because people can scroll with the mouse cursor and I need to disable that, I thought about canceling MouseEvents when they're fired but I couldn't get it to work (the listener that is.)
If someone knows how then please speak up and tell me! :)
EDIT: Whoops, I forgot my code. Here is my Console class.
This is started by another class with new Console();
EDIT 2: Here are some snippets of me trying to create an invisible cursor and a mouse listener. The first one works, but the latter does not.
// Invisible cursor
Toolkit toolkit = Toolkit.getDefaultToolkit();
Point hotSpot = new Point(0,0);
BufferedImage cursorImage = new BufferedImage(1, 1, BufferedImage.TRANSLUCENT);
Cursor invisibleCursor = toolkit.createCustomCursor(cursorImage, hotSpot, "InvisibleCursor");
frame.setCursor(invisibleCursor);
// Adding mouse listener
frame.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent me) {
System.out.println(me);
}
});
EDIT 3: To elaborate on the mouse listener it simply does not work. It doesn't print anything.
If you just want to prevent users from seeing old text, remove the old text from the JTextArea.
The easiest way to do it is to leave the JTextArea in a JScrollPane, and keep track of the lines yourself:
private static final int MAX_VISIBLE_LINES = 12;
private final Deque<String> lines = new LinkedList<>();
void appendLine(String line,
JTextArea textArea) {
lines.addLast(line);
if (lines.size() > MAX_VISIBLE_LINES) {
lines.removeFirst();
}
String text = String.join("\n", lines);
textArea.setText(text);
textArea.setCaretPosition(text.length());
try {
textArea.scrollRectToVisible(
textArea.modelToView(text.length()));
} catch (BadLocationException e) {
throw new RuntimeException(e);
}
}
Trying to commandeer the mouse on a multitasking desktop is just going to make users angry. Would you want an application preventing you from reading your e-mail?
Update:
If you want to base the number of lines of text on the JTextArea’s current height, use the JTextArea’s font metrics. I assume you don’t need to get it exactly right and it’s okay if the number is off by one or two lines. (To account for things like line wrapping would be considerably more difficult.)
private final Deque<String> lines = new LinkedList<>();
void appendLine(String line,
JTextArea textArea) {
FontMetrics metrics = textArea.getFontMetrics(textArea.getFont());
JViewport viewport = (JViewport) textArea.getParent();
int visibleLineCount = viewport.getExtentSize().height / metrics.getHeight();
lines.addLast(line);
while (lines.size() > visibleLineCount) {
lines.removeFirst();
}
String text = String.join("\n", lines);
textArea.setText(text);
textArea.setCaretPosition(text.length());
try {
textArea.scrollRectToVisible(
textArea.modelToView(text.length()));
} catch (BadLocationException e) {
throw new RuntimeException(e);
}
}
I would like to know the best way to approach what I am trying to achieve, I can't figure out the logical path I should take.
I have a JTextField and a JTextButton, when input is added to the JTextField and either enter or the button is pressed, it will display on the JTextArea. Now, what I want is to choose when and what the JTextArea and Button do.
For example I want default Enter & Button to display next append text in my code. Then when a case is presented I want the JTextField to only accept either int or string and then once completed, I want it to go back to default.
I don't know if what I am trying to do is logical or best practice...
The idea behind this is, I have a story text based gui game. I want it to display text to the JTextArea and when Enter or button is pressed to display the next line of text and when in the story it requires user input, the JTextArea will look for that input.
So far I have an EventListener and ActionListener which submits what I type from JTextField to JTextArea, but that is about it.
Thanks for your assistance! I have solved my issue, not sure if this is the "Best Solution". I combined your solution with a bit of tweaking.
In this instance, buttonState is an int which can be changed throughout my code by calling a constructor "setButtonState". I could have made buttonState a static to make things easier, but thought I could keep things clean.
enterButton.addActionListener(new ActionListener()
{ //This is used so when the enter screen button is pressed, it will submit text from text field to text area.
public void actionPerformed(ActionEvent e) {
String text = inputTextField.getText();
InputTextFieldEvent event = new InputTextFieldEvent(this, text);
if (buttonState == 0) //Displays all text in JTextField to JTextArea, mostly for testing purposes.
{
if (textInputListener != null) {
textInputListener.setInputListenerOccurred(event);
}
}
if (buttonState == 1) //Only accepts string for answer
{
if (inputTextField.getText().matches("[a-zA-Z]+"))
{
textInputListener.setInputListenerOccurred(event);
}
else
{
getAppendMainTextArea("You have entered an invalid input, only letters are allowed.");
}
}
if (buttonState == 2) //Only accepts int for answer
{
if (inputTextField.getText().matches("[0-9]+"))
{
textInputListener.setInputListenerOccurred(event);
}
else
{
getAppendMainTextArea("You have entered an invalid input, only numbers are allowed.");
}
}
}
});
I've created a number picker with some string values.
public void setValues() {
boolean defaultAvaliable = false;
int defaultRow = 0;
String[] values = new String[pickValues.size()];
for (int i = 0; i < pickValues.size(); i++) {
if (pickValues.get(i).equals("01:00")) {
defaultAvaliable = true;
defaultRow = i;
}
values[i] = pickValues.get(i);
}
timePicker.setMaxValue(values.length - 1);
timePicker.setMinValue(0);
timePicker.setWrapSelectorWheel(false);
timePicker.setDisplayedValues(values);
if (defaultAvaliable) {
timePicker.setValue(defaultRow);
}
}
And when I scroll to edge values (first or last) the picker first jumps to that item in a weird, not smooth way and after that when i try to go back to other values it doesn't scroll. The picker glitches on the edge value and after some time it finally goes back. In some way it looks like I would scroll over the edge value and it needs some scrolling to get back.
Has anyone else had this problem or does anyone have any suggestions what to check?
EDIT 1:
So I've got to he point that I know the problem is when I change the font size in my numberpicker, and don't know why. As soon as I set the font size over 39 it starts to get stuck on first and last item.
private void updateView(View view) {
if(view instanceof EditText){
((EditText) view).setTextSize(39);
((EditText) view).setTextColor(getResources().getColor(R.color.mp_white));
((EditText) view).setFocusable(false);
}
}
Does anyone know what is the row size?
Or if I have to change the size of it, so it detects the scrolling correctly?
Assume this very small program:
1. package ex1;
2. public interface Resizable {
3. void resize();
4. }
In my editor, if I select line 2-3 using mouse and say click on a button, I want to highlight these texts and also print, which line numbers were selected exactly for the button.
I can do the highlighting part, but I don't know how to find the line numbers of highlighted texts, As I think, I should use a listener, which will detect any changes in editor.
I think I should use an action listener, which will detect when the button is pressed after selecting text blocks. But how I will know, which lines are selected exactly?
The start and end of the highlight can be taken from the caret position dot and mark respectively. These are offsets in the Document. You must then calculate the number of newlines from the start of the document until the mark/do
textArea.addCaretListener(new CaretListener() {
#Override
public void caretUpdate(CaretEvent e) {
int startLine = getLine(e.getDot());
int endLine = getLine(e.getMark());
...
}
});
private int getLine(int offset) {
String text = textArea.getDocument().getText(0, offset);
int linenr = 0;
int idx = text.indexOf("\n");
while (idx != -1) {
linenr++;
idx = text.indexOf("\n", idx);
}
return linenr;
}
I'm having trouble with a contentPane. Here's the code in question:
public void graph() {
JFrame frame = new JFrame("Graph");
Graph[] graphs = new Graph[timeSlices];
int k = 0;
for (TreeMap<MyPoint, BigDecimal> prevU : prevUs) {
graphs[k] = new Graph(prevU);
k++;
}
// The KeyList handles switching between graphs.
frame.addKeyListener(new KeyList(frame.getContentPane(), graphs));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(810, 500);
frame.setVisible(true);
}
private class KeyList extends KeyAdapter {
private Container contentPane;
private Graph[] graphs;
private int index;
public KeyList(Container contentPane, Graph[] graphs) {
this.contentPane = contentPane;
this.graphs = graphs;
this.index = 0;
this.contentPane.add(this.graphs[0]);
}
public void keyPressed(KeyEvent e) {
// Go back a time step
if (e.getKeyCode() == KeyEvent.VK_LEFT && index > 0) {
contentPane.remove(graphs[index]);
contentPane.add(graphs[--index]);
contentPane.validate();
System.out.println(index);
}
// Go forward a time step
else if (e.getKeyCode() == KeyEvent.VK_RIGHT && index < timeSlices - 1) {
contentPane.remove(graphs[index]);
contentPane.add(graphs[++index]);
contentPane.validate();
System.out.println(index);
}
// Exit if Esc is hit
else if (e.getKeyCode() == KeyEvent.VK_ESCAPE)
System.exit(0);
}
}
Graph is just a Component, easy peasy. When I hit the right arrow, I want to replace the currently displayed Graph with the next one in the array, and when I hit the left arrow, I want to replace the Graph with the previous one in the array.
The weird thing is that when I hit right, it works just fine. However, when I hit left, the Graph doesn't change. The index changes, thus I know the code is being reached, but the GUI doesn't change.
Now get ready for this. When I comment out the right key's validate line, the left one will work about half the time. What is going on there? Here's the rest of the code if you want to run and see for your self (just one file) : http://pastebin.com/qWxWrypK. The starting paramemters I'm currently using are T=1, dt=.01, L=1, h=.05.
I was looking into it, I thought it might be because the contentPane of a JFrame is really a JPanel, but that line of thinking didn't get anywhere...
Thanks for any help
Edit:
So I'm still working with it. Here's another weird thing. If I set the index in the KeyList class to timeSlices-1 (basically getting the last Graph in graphs array), and I hit left, it works! But, now the right doesn't! Something weird has to be going on with the array or something because the index changes just fine. Hmm.
Edit:
Something's going on with the array. For some reason, the Graph can only be used once. Perhaps it's being destroyed on removal? Or something like that...
Instead of trying to remove/add panels to a container use a CardLayout which was designed for this purpose.
Also, don't use KeyListeners. Instead you should be using Key Bindings. Then you simply bind the next/previous keys to the next/previous methods of the card layout.