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    }