001 package nl.cwi.sen1.gui.plugin.editor; 002 003 import java.awt.Color; 004 import java.awt.Component; 005 import java.awt.Event; 006 import java.awt.Graphics; 007 import java.awt.Graphics2D; 008 import java.awt.Rectangle; 009 import java.awt.RenderingHints; 010 import java.awt.event.KeyEvent; 011 import java.awt.event.MouseEvent; 012 import java.awt.event.MouseMotionAdapter; 013 import java.util.Iterator; 014 import java.util.LinkedList; 015 import java.util.List; 016 017 import javax.swing.Action; 018 import javax.swing.JMenu; 019 import javax.swing.JMenuItem; 020 import javax.swing.JTextPane; 021 import javax.swing.KeyStroke; 022 import javax.swing.event.CaretEvent; 023 import javax.swing.event.CaretListener; 024 import javax.swing.event.DocumentEvent; 025 import javax.swing.event.DocumentListener; 026 import javax.swing.event.UndoableEditListener; 027 import javax.swing.plaf.ComponentUI; 028 import javax.swing.text.BadLocationException; 029 import javax.swing.text.DefaultEditorKit; 030 import javax.swing.text.Element; 031 import javax.swing.text.Highlighter; 032 import javax.swing.text.Style; 033 import javax.swing.text.StyleContext; 034 import javax.swing.text.DefaultHighlighter.DefaultHighlightPainter; 035 import javax.swing.text.Highlighter.HighlightPainter; 036 037 import nl.cwi.sen1.gui.plugin.EditorModifiedEvent; 038 import nl.cwi.sen1.gui.plugin.EditorModifiedListener; 039 040 public class EditorPane extends JTextPane { 041 private Style defaultStyle; 042 043 private Object focusTag; 044 045 private HighlightPainter focusPainter; 046 047 private int bracketOffset; 048 049 private Object bracketTag; 050 051 private BracketHighlightPainter bracketPainter; 052 053 private Object jaggedSelectionTag; 054 055 private JaggedHighlightPainter jaggedHighlightPainter; 056 057 private boolean modified; 058 059 private boolean undoableEditListenerAdded = false; 060 061 private JMenu menu; 062 063 private Color bracketHighlightColor = Color.GRAY; 064 065 private Color lineHighlightColor = new Color(232, 242, 254); 066 067 private Color backgroundColor = Color.WHITE; 068 069 private Rectangle lineHighlight = new Rectangle(0, 0, 0, 0); 070 071 private List<EditorModifiedListener> listeners = new LinkedList<EditorModifiedListener>(); 072 073 public EditorPane() { 074 setEditorKit(new EditorKit()); 075 076 setOpaque(false); 077 focusPainter = new DefaultHighlightPainter(Color.BLACK); 078 jaggedHighlightPainter = new JaggedHighlightPainter(Color.RED); 079 bracketPainter = new BracketHighlightPainter(bracketHighlightColor); 080 081 defaultStyle = StyleContext.getDefaultStyleContext().getStyle( 082 StyleContext.DEFAULT_STYLE); 083 084 getStyledDocument().setLogicalStyle(0, defaultStyle); 085 086 modified = false; 087 088 addBindings(); 089 addDocumentListener(); 090 addCaretListener(); 091 addMouseMotionListener(); 092 } 093 094 protected void addBindings() { 095 menu = new JMenu("Edit"); 096 097 addBinding(menu, KeyEvent.VK_Z, EditorKit.undoAction); 098 addBinding(menu, KeyEvent.VK_Y, EditorKit.redoAction); 099 addBinding(menu, KeyEvent.VK_F, EditorKit.findAction); 100 addBinding(menu, KeyEvent.VK_G, EditorKit.gotoLineAction); 101 addBinding(menu, KeyEvent.VK_C, DefaultEditorKit.copyAction); 102 addBinding(menu, KeyEvent.VK_X, DefaultEditorKit.cutAction); 103 addBinding(menu, KeyEvent.VK_V, DefaultEditorKit.pasteAction); 104 addBinding(KeyEvent.VK_D, EditorKit.deleteLineAction); 105 addBinding(menu, KeyEvent.VK_5, EditorKit.gotoMatchingBracketAction); 106 addBinding(menu, KeyEvent.VK_J, EditorKit.selectFocusAction); 107 addBinding(KeyEvent.VK_BACK_SPACE, EditorKit.deletePreviousWordAction); 108 addBinding(KeyEvent.VK_DELETE, EditorKit.deleteNextWordAction); 109 } 110 111 protected void addBinding(int key, String name) { 112 KeyStroke keyStroke = KeyStroke.getKeyStroke(key, Event.CTRL_MASK); 113 getInputMap().put(keyStroke, name); 114 } 115 116 protected void addBinding(JMenu menu, int key, String name) { 117 KeyStroke keyStroke = KeyStroke.getKeyStroke(key, Event.CTRL_MASK); 118 getInputMap().put(keyStroke, name); 119 120 Action action = ((EditorKit) getEditorKit()).getAction(name); 121 JMenuItem item = new JMenuItem(action); 122 item.setAccelerator(keyStroke); 123 menu.add(item); 124 } 125 126 private void addMouseMotionListener() { 127 addMouseMotionListener(new MouseMotionAdapter() { 128 public void mouseDragged(MouseEvent e) { 129 clearFocus(); 130 } 131 }); 132 } 133 134 private void addCaretListener() { 135 addCaretListener(new CaretListener() { 136 public void caretUpdate(CaretEvent e) { 137 if (getSelectionStart() != getSelectionEnd()) { 138 clearFocus(); 139 } 140 try { 141 paintLineHighlight(); 142 paintBracketHighlight(); 143 } catch (BadLocationException e1) { 144 } 145 } 146 }); 147 } 148 149 private void addDocumentListener() { 150 getDocument().addDocumentListener(new DocumentListener() { 151 public void changedUpdate(DocumentEvent e) { 152 } 153 154 public void insertUpdate(DocumentEvent e) { 155 setCharacterAttributes(defaultStyle, true); 156 modified = true; 157 fireEditorModifiedEvent(); 158 } 159 160 public void removeUpdate(DocumentEvent e) { 161 modified = true; 162 fireEditorModifiedEvent(); 163 } 164 165 166 }); 167 } 168 169 private void paintLineHighlight() { 170 Element root = this.getDocument().getDefaultRootElement(); 171 int line = root.getElementIndex(getCaretPosition()); 172 Element lineElement = root.getElement(line); 173 int offset = lineElement.getStartOffset(); 174 175 highlight(offset); 176 } 177 178 private void paintBracketHighlight() throws BadLocationException { 179 bracketOffset = TextUtilities.findMatchingBracket(getDocument(), 180 getCaretPosition() - 1); 181 if (bracketTag != null) { 182 getHighlighter().removeHighlight(bracketTag); 183 } 184 if (bracketOffset != -1) { 185 Highlighter.HighlightPainter p = bracketPainter; 186 bracketTag = getHighlighter().addHighlight(bracketOffset, 187 bracketOffset + 1, p); 188 } 189 } 190 191 public void highlight(int offset) { 192 try { 193 Rectangle r = modelToView(offset); 194 r.x = 0; 195 r.width = getWidth(); 196 if (!r.equals(lineHighlight)) { 197 // remove previous linehighlight 198 repaint(lineHighlight); 199 lineHighlight = r; 200 repaint(lineHighlight); 201 } 202 } catch (Exception e) { 203 } 204 } 205 206 public void paintComponent(Graphics gfx) { 207 gfx.setColor(backgroundColor); 208 Rectangle r = getVisibleRect(); 209 gfx.fillRect(r.x, r.y, r.width, r.height); 210 211 Graphics2D g2 = (Graphics2D) gfx; 212 g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, 213 RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 214 g2.setRenderingHint(RenderingHints.KEY_RENDERING, 215 RenderingHints.VALUE_RENDER_QUALITY); 216 217 gfx.setColor(lineHighlightColor); 218 gfx.fillRect(lineHighlight.x, lineHighlight.y, lineHighlight.width, 219 lineHighlight.height); 220 221 super.paintComponent(g2); 222 } 223 224 public boolean getScrollableTracksViewportWidth() { 225 Component parent = getParent(); 226 ComponentUI ui = getUI(); 227 228 return parent != null ? (ui.getPreferredSize(this).width <= parent 229 .getSize().width) : true; 230 } 231 232 public void setFocusColor(Color color) { 233 focusPainter = new DefaultHighlightPainter(color); 234 } 235 236 public Style getDefaultStyle() { 237 return defaultStyle; 238 } 239 240 public void setStyle(int offset, int length, String name) { 241 Style style = getStyle(name); 242 243 if (style != null) { 244 getStyledDocument().setCharacterAttributes(offset, length, style, 245 true); 246 } else { 247 System.err.println("No such style available:" + name); 248 } 249 } 250 251 public void unsetStyles() { 252 getStyledDocument().setCharacterAttributes(0, 253 getDocument().getLength(), defaultStyle, true); 254 } 255 256 public void focus(int offset, int length) { 257 clearFocus(); 258 259 try { 260 focusTag = getHighlighter().addHighlight(offset, length, 261 focusPainter); 262 } catch (BadLocationException e) { 263 // happens when you give an offset/length outside the editor 264 } 265 } 266 267 public void clearFocus() { 268 if (focusTag != null) { 269 getHighlighter().removeHighlight(focusTag); 270 } 271 focusTag = null; 272 } 273 274 public Object getFocusTag() { 275 return focusTag; 276 } 277 278 public void jag(int offset, int length) { 279 clearJaggedSelection(); 280 281 try { 282 jaggedSelectionTag = getHighlighter().addHighlight(offset, length, jaggedHighlightPainter); 283 } catch (BadLocationException e) { 284 // happens when you give an offset/length outside the editor 285 } 286 } 287 288 public void clearJaggedSelection() { 289 if (jaggedSelectionTag != null) { 290 getHighlighter().removeHighlight(jaggedSelectionTag); 291 } 292 jaggedSelectionTag = null; 293 } 294 295 public Object getJaggedSelectionTag() { 296 return jaggedSelectionTag; 297 } 298 299 public Color getBackgroundColor() { 300 return super.getBackground(); 301 } 302 303 public JMenu getEditorMenu() { 304 return menu; 305 } 306 307 public void setText(String t) { 308 UndoableEditListener undoListener = ((EditorKit) getEditorKit()) 309 .getUndoListener(); 310 311 ((EditorKit) getEditorKit()).startUndoableSequence(); 312 super.setText(t); 313 ((EditorKit) getEditorKit()).endUndoableSequence(); 314 315 modified = false; 316 if (!undoableEditListenerAdded) { 317 undoableEditListenerAdded = true; 318 getDocument().addUndoableEditListener(undoListener); 319 } 320 } 321 322 public void setCaretPositionAtEnd() { 323 setCaretPosition(getDocument().getLength()); 324 } 325 326 public void setModified(boolean modified) { 327 this.modified = modified; 328 } 329 330 public boolean isModified() { 331 return modified; 332 } 333 334 public boolean find(String toBeFound, boolean caseInsensitive, 335 boolean wrapAround, boolean forward) { 336 String text = getText(); 337 338 if (caseInsensitive) { 339 toBeFound = toBeFound.toLowerCase(); 340 text = text.toLowerCase(); 341 } 342 343 int from; 344 345 if (forward) { 346 from = getCaretPosition(); 347 } else { 348 from = getSelectionStart() - 1; 349 } 350 351 int index = findFromInText(text, toBeFound, from, forward); 352 if (wrapAround && index == -1) { 353 if (forward) { 354 findFromInText(text, toBeFound, 0, forward); 355 } else { 356 findFromInText(text, toBeFound, getDocument().getLength(), 357 forward); 358 } 359 } 360 361 return index != -1; 362 } 363 364 private int findFromInText(String text, String toBeFound, int from, 365 boolean forward) { 366 int index = -1; 367 368 if (toBeFound.length() == 0) { 369 return index; 370 } 371 372 if (forward) { 373 index = text.indexOf(toBeFound, from); 374 } else { 375 index = text.lastIndexOf(toBeFound, from); 376 } 377 378 if (index != -1) { 379 setSelectionStart(index); 380 setSelectionEnd(index + toBeFound.length()); 381 } 382 return index; 383 } 384 385 public boolean replace(String toBeFound, String toReplaceWith, 386 boolean caseInsensitive, boolean wrapAround, boolean forward) { 387 if (find(toBeFound, caseInsensitive, wrapAround, forward)) { 388 replaceSelection(toReplaceWith); 389 return true; 390 } 391 return false; 392 } 393 394 public void replaceAll(String toBeFound, String toReplaceWith, 395 boolean caseInsensitive) { 396 int cursor = getCaretPosition(); 397 setCaretPosition(0); 398 while (replace(toBeFound, toReplaceWith, caseInsensitive, false, true)) { 399 } 400 if (cursor > getDocument().getLength()) { 401 setCaretPositionAtEnd(); 402 } else { 403 setCaretPosition(cursor); 404 } 405 } 406 407 public void gotoLine(int line) { 408 String text = getText(); 409 int index = -1; 410 int i = 1; 411 412 if (line == -1) { 413 return; 414 } 415 416 if (line != 1) { 417 do { 418 index = text.indexOf('\n', index + 1); 419 if (index != -1) { 420 i++; 421 } 422 } while (index != -1 && i < line); 423 if (index != -1) { 424 index++; 425 } 426 } else { 427 index = 0; 428 } 429 430 if (index != -1) { 431 setCaretPosition(index < text.length() ? index : index - 1); 432 } 433 } 434 435 public void addEditorModifiedListener(EditorModifiedListener l) { 436 listeners.add(l); 437 } 438 439 public void removeEditorModifiedListener(EditorModifiedListener l) { 440 listeners.remove(l); 441 } 442 443 public void fireEditorModifiedEvent() { 444 EditorModifiedEvent e = new EditorModifiedEvent(this); 445 Iterator<EditorModifiedListener> iter = listeners.iterator(); 446 while (iter.hasNext()) { 447 iter.next().editorModified(e); 448 } 449 clearJaggedSelection(); 450 } 451 }