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}