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 }