001package arcommon.ui.component;
002
003import javax.swing.JTextField;
004import javax.swing.text.DefaultCaret;
005
006import arcommon.StringUtil;
007import arcommon.ui.other.MyUndoManager;
008
009/**
010 * A {@link javax.swing.JTextField} with undo.
011 * <br>
012 * Undo is clear (by default) when {@link #setText(String)} is called.<br>
013 * Note: Uses {@link javax.swing.text.DefaultCaret#NEVER_UPDATE} instead of the default {@link javax.swing.text.DefaultCaret#UPDATE_WHEN_ON_EDT} when {@link #setText(String)} is called.
014 */
015public class MyJTextField extends JTextField {
016
017    private static final long serialVersionUID = 6591496933833049128L;
018    
019    private MyUndoManager myUndoManager;
020    private boolean moveCaretInitial;
021
022    public MyJTextField() {
023        init();
024    }
025    
026    public MyJTextField(int columns) {
027        super(columns);
028        init();
029    }
030    
031    public MyJTextField(String text) {
032        super(text);
033        init();
034    }
035    
036    private void init() {
037        myUndoManager = MyUndoManager.addUndoToTextComponent(this);
038        moveCaretInitial = true;
039    }
040    
041    /**
042     * See {@link #setText(String, boolean, boolean)} for details
043     */
044    @Override
045    public void setText(String text) {
046        setText(text, true, moveCaretInitial);
047    }
048    
049    /**
050     * Sets the text of the text field without moving the caret.
051     * <br>
052     * If {@code discardsAllEdits} is true the undo history is removed.<br>
053     * if {@code moveCaretInitial} is true the caret to it's initial position ({@code text.length()}).<br>
054     * See {@link arcommon.ui.component.MyJTextArea#setText(String, boolean, boolean)} for at very similar method.
055     */
056    public void setText(String text, boolean discardsAllEdits, boolean pmoveCaretInitial) {
057        // Code copied from https://stackoverflow.com/questions/1627028/how-to-set-auto-scrolling-of-jtextarea-in-java-gui
058        // and modified a lot
059        if (!StringUtil.stringEquals(text, getText())) {
060            DefaultCaret caret = (DefaultCaret)getCaret();
061            int updatePolicy = caret.getUpdatePolicy();
062            // Set UpdatePolicy to DefaultCaret.NEVER_UPDATE to avoid that the caret is moved to end of the text area when setText() is used.
063            caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
064            
065            int oldCaretPosition = getCaretPosition();
066            if (myUndoManager != null) { // This is null is setText is called in super constructor.
067                myUndoManager.nextUndoIsDouble();
068            }
069            super.setText(text);
070            int newCaretPosition = getCaretPosition();
071            if (oldCaretPosition != newCaretPosition) {
072                if (text == null) {
073                    oldCaretPosition = 0;
074                } else if (oldCaretPosition > text.length()) {
075                    oldCaretPosition = text.length();
076                }
077                // Move the caret position to the position that the documentation states that is it should be in.
078                setCaretPosition(oldCaretPosition);
079            }
080            
081            caret.setUpdatePolicy(updatePolicy); // Set UpdatePolicy back
082        }
083        
084        if (pmoveCaretInitial) {
085            if (text != null) {
086                setCaretPosition(text.length());
087            }
088            moveCaretInitial = false;
089        }
090        
091        if (discardsAllEdits && myUndoManager != null) { // myUndoManager is null is setText is called in super constructor.
092            myUndoManager.discardAllEdits();
093        }
094    }
095}