001package arcommon.ui.component;
002
003import java.awt.Color;
004
005import javax.swing.JComponent;
006import javax.swing.JTextArea;
007import javax.swing.event.CaretListener;
008import javax.swing.text.BadLocationException;
009import javax.swing.text.Caret;
010import javax.swing.text.DefaultCaret;
011
012import arcommon.StringUtil;
013import arcommon.ui.other.MyUndoManager;
014
015/**
016 * A {@link javax.swing.JTextArea} with undo in a {@link javax.swing.JScrollPane}.
017 * <br>
018 * Undo is cleared (by default) when {@link #setText(String)} is called.<br>
019 * 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.
020 */
021public class MyJTextArea extends AbstractDelegatingJScrollPane {
022    
023    private static final long serialVersionUID = 7195806586966402037L;
024    
025    // This class is a hack to make super.getRowHeight() visible.
026    private class JTextAreaHack extends JTextArea {
027        private static final long serialVersionUID = -434197405913512358L;
028
029        public int getRowHeight() {
030            return super.getRowHeight();
031        }
032    }
033    
034    private MyUndoManager myUndoManager;
035    private JTextAreaHack textArea;
036    private boolean moveCaretInitial;
037    
038    public MyJTextArea() {
039        textArea = new JTextAreaHack();
040        
041        moveCaretInitial = true;
042        
043        initActionMap();
044        
045        setViewportView(textArea);
046                
047        myUndoManager = MyUndoManager.addUndoToTextComponent(textArea);
048    }
049    
050    public void addCaretListener(CaretListener caretListener) {
051        textArea.addCaretListener(caretListener);
052    }
053    
054    public void setCaret(Caret caret) {
055        textArea.setCaret(caret);
056    }
057    
058    public void setCaretPosition(int position) {
059        textArea.setCaretPosition(position);
060    }
061    
062    public int getCaretPosition() {
063        return textArea.getCaretPosition();
064    }
065    
066    /**
067     * See {@link #setText(String, boolean, boolean)} for details
068     */
069    public void setText(String text) {
070        setText(text, true, moveCaretInitial);
071    }
072    
073    /**
074     * Sets the text of the text field without moving the caret.
075     * <br>
076     * If {@code discardsAllEdits} is true the undo history is removed.<br>
077     * if {@code moveCaretInitial} is true the caret to it's initial position (0).<br>
078     * See {@link arcommon.ui.component.MyJTextField#setText(String, boolean, boolean)} for at very similar method.
079     */
080    public void setText(String text, boolean discardsAllEdits, boolean pmoveCaretInitial) {
081        // Code copied from https://stackoverflow.com/questions/1627028/how-to-set-auto-scrolling-of-jtextarea-in-java-gui
082        // and modified a lot
083        if (!StringUtil.stringEquals(text, getText())) {
084            DefaultCaret caret = (DefaultCaret)textArea.getCaret();
085            int updatePolicy = caret.getUpdatePolicy();
086            // Set UpdatePolicy to DefaultCaret.NEVER_UPDATE to avoid that the caret is moved to end of the text area when setText() is used.
087            caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
088            
089            int oldCaretPosition = getCaretPosition();
090            myUndoManager.nextUndoIsDouble();
091            textArea.setText(text);
092            int newCaretPosition = getCaretPosition();
093            if (oldCaretPosition != newCaretPosition) {
094                if (text == null) {
095                    oldCaretPosition = 0;
096                } else if (oldCaretPosition > text.length()) {
097                    oldCaretPosition = text.length();
098                }
099                // Move the caret position to the position that the documentation states that is it should be in.
100                setCaretPosition(oldCaretPosition);
101            }
102            
103            caret.setUpdatePolicy(updatePolicy); // Set UpdatePolicy back
104        }
105        
106        if (pmoveCaretInitial) {
107            setCaretPosition(0);
108            moveCaretInitial = false;
109        }
110        
111        if (discardsAllEdits) {
112            myUndoManager.discardAllEdits();
113        }
114    }
115    
116    public String getText() {
117        return textArea.getText();
118    }
119    
120    public String getText(int offset, int length) {
121        try {
122            return textArea.getText(offset, length);
123        } catch (BadLocationException e) {
124            throw new RuntimeException(e);
125        }
126    }
127    
128    public void setEditable(boolean editable) {
129        textArea.setEditable(editable);
130    }
131    
132    public boolean getLineWrap() {
133        return textArea.getLineWrap();
134    }
135    
136    public void setLineWrap(boolean wrap) {
137        textArea.setLineWrap(wrap);
138    }
139    
140    public boolean getWrapStyleWord() {
141        return textArea.getWrapStyleWord();
142    }
143    
144    public void setWrapStyleWord(boolean word) {
145        textArea.setWrapStyleWord(word);
146    }
147    
148    public void setDisabledTextColor(Color color) {
149        textArea.setDisabledTextColor(color);
150    }
151    
152    public String getSelectedText() {
153        return textArea.getSelectedText();
154    }
155    
156    public void select(int selectionStart, int selectionEnd) {
157        textArea.select(selectionStart, selectionEnd);
158    }
159    
160    public int getSelectionStart() {
161        return textArea.getSelectionStart();
162    }
163
164    public int getSelectionEnd() {
165        return textArea.getSelectionEnd();
166    }
167    
168    public int getRowHeight() {
169        return textArea.getRowHeight();
170    }
171    
172    public int getLineOfOffset(int offset) {
173        try {
174            return textArea.getLineOfOffset(offset);
175        } catch (BadLocationException e) {
176            throw new RuntimeException(e);
177        }
178    }
179    
180    public int getLineCount() {
181        return textArea.getLineCount();
182    }
183
184    public int getLineStartOffset(int line) {
185        try {
186            return textArea.getLineStartOffset(line);
187        } catch (BadLocationException e) {
188            throw new RuntimeException(e);
189        }
190    }
191    
192    public int getLineEndOffset(int line) {
193        try {
194            return textArea.getLineEndOffset(line);
195        } catch (BadLocationException e) {
196            throw new RuntimeException(e);
197        }
198    }
199    
200    public int getTabSize() {
201        return textArea.getTabSize();
202    }
203    
204    public void setTabSize(int size) {
205        textArea.setTabSize(size);
206    }
207    
208    @Override
209    protected JComponent getDelegatingJComponent() {
210        return textArea;
211    }
212}