001: /*
002: * Sun Public License Notice
003: *
004: * The contents of this file are subject to the Sun Public License
005: * Version 1.0 (the "License"). You may not use this file except in
006: * compliance with the License. A copy of the License is available at
007: * http://www.sun.com/
008: *
009: * The Original Code is NetBeans. The Initial Developer of the Original
010: * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
011: * Microsystems, Inc. All Rights Reserved.
012: */
013:
014: package org.netbeans.editor.ext;
015:
016: import java.awt.Font;
017: import java.awt.Rectangle;
018: import java.awt.event.ActionEvent;
019: import java.awt.event.ActionListener;
020: import java.awt.event.MouseEvent;
021:
022: import javax.swing.SwingUtilities;
023: import javax.swing.Timer;
024: import javax.swing.text.BadLocationException;
025: import javax.swing.text.JTextComponent;
026:
027: import org.netbeans.editor.BaseCaret;
028: import org.netbeans.editor.BaseDocument;
029: import org.netbeans.editor.Coloring;
030: import org.netbeans.editor.DrawContext;
031: import org.netbeans.editor.DrawLayer;
032: import org.netbeans.editor.DrawLayerFactory;
033: import org.netbeans.editor.EditorUI;
034: import org.netbeans.editor.InvalidMarkException;
035: import org.netbeans.editor.MarkFactory;
036: import org.netbeans.editor.SettingsChangeEvent;
037: import org.netbeans.editor.SettingsUtil;
038: import org.netbeans.editor.Utilities;
039: import org.netbeans.editor.WeakTimerListener;
040:
041: /**
042: * Extended caret implementation
043: *
044: * @author Miloslav Metelka
045: * @version 1.00
046: */
047:
048: public class ExtCaret extends BaseCaret {
049:
050: /** Highlight row draw layer name */
051: public static final String HIGHLIGHT_ROW_LAYER_NAME = "highlight-row-layer"; // NOI18N
052:
053: /** Highlight row draw layer visibility */
054: public static final int HIGHLIGHT_ROW_LAYER_VISIBILITY = 1600;
055:
056: /** Highlight matching brace draw layer name */
057: public static final String HIGHLIGHT_BRACE_LAYER_NAME = "highlight-brace-layer"; // NOI18N
058:
059: /** Highlight matching brace draw layer visibility */
060: public static final int HIGHLIGHT_BRACE_LAYER_VISIBILITY = 11000;
061:
062: /**
063: * Whether to highlight the background of the row where the caret is.
064: */
065: boolean highlightRow;
066:
067: /** Whether to hightlight the matching brace */
068: boolean highlightBrace;
069:
070: /** Coloring used for highlighting the row where the caret is. */
071: Coloring highlightRowColoring;
072:
073: /** Coloring used for highlighting the matching brace */
074: Coloring highlightBraceColoring;
075:
076: /** Mark holding the start of the line where the caret currently is. */
077: MarkFactory.DrawMark highlightRowMark;
078:
079: /** Mark holding the starting position of the matching brace. */
080: MarkFactory.DrawMark highlightBraceStartMark;
081:
082: /** Mark holding the ending position of the matching brace. */
083: MarkFactory.DrawMark highlightBraceEndMark;
084:
085: /** Timer that fires when the matching brace should be displayed */
086: private Timer braceTimer;
087: private ActionListener braceTimerListener; // because of unwanted GC
088:
089: /**
090: * Signal that the next matching brace update will be immediate without
091: * waiting for the brace timer to fire the action.
092: */
093: private boolean matchBraceUpdateSync;
094:
095: /**
096: * Whether the brace starting and ending marks are currently valid or not.
097: * If they are not valid the block they delimit is not highlighted.
098: */
099: boolean braceMarksValid;
100:
101: boolean simpleMatchBrace;
102:
103: private boolean popupMenuEnabled;
104:
105: static final long serialVersionUID = -4292670043122577690L;
106:
107: protected void modelChanged(BaseDocument oldDoc, BaseDocument newDoc) {
108: // Fix for #7108
109: braceMarksValid = false; // brace marks are out of date - new
110: // document
111: if (highlightRowMark != null) {
112: try {
113: highlightRowMark.remove();
114: } catch (InvalidMarkException e) {
115: }
116: highlightRowMark = null;
117: }
118:
119: if (highlightBraceStartMark != null) {
120: try {
121: highlightBraceStartMark.remove();
122: } catch (InvalidMarkException e) {
123: }
124: highlightBraceStartMark = null;
125: }
126:
127: if (highlightBraceEndMark != null) {
128: try {
129: highlightBraceEndMark.remove();
130: } catch (InvalidMarkException e) {
131: }
132: highlightBraceEndMark = null;
133: }
134:
135: super .modelChanged(oldDoc, newDoc);
136: }
137:
138: /**
139: * Called when settings were changed. The method is called also in
140: * constructor, so the code must count with the evt being null.
141: */
142: public void settingsChange(SettingsChangeEvent evt) {
143: super .settingsChange(evt);
144: JTextComponent c = component;
145: if (c != null) {
146:
147: EditorUI editorUI = Utilities.getEditorUI(c);
148: Class kitClass = Utilities.getKitClass(c);
149: highlightRowColoring = editorUI
150: .getColoring(ExtSettingsNames.HIGHLIGHT_CARET_ROW_COLORING);
151: highlightBraceColoring = editorUI
152: .getColoring(ExtSettingsNames.HIGHLIGHT_MATCH_BRACE_COLORING);
153:
154: // Handle highlight row
155: boolean oldHighlightRow = highlightRow;
156: highlightRow = SettingsUtil.getBoolean(kitClass,
157: ExtSettingsNames.HIGHLIGHT_CARET_ROW,
158: ExtSettingsDefaults.defaultHighlightCaretRow);
159:
160: if (oldHighlightRow && !highlightRow
161: && highlightRowMark != null) {
162: try {
163: highlightRowMark.remove();
164: } catch (InvalidMarkException e) {
165: }
166:
167: highlightRowMark = null;
168: }
169:
170: highlightBrace = SettingsUtil.getBoolean(kitClass,
171: ExtSettingsNames.HIGHLIGHT_MATCH_BRACE,
172: ExtSettingsDefaults.defaultHighlightMatchBrace);
173: int highlightBraceDelay = SettingsUtil
174: .getInteger(
175: kitClass,
176: ExtSettingsNames.HIGHLIGHT_MATCH_BRACE_DELAY,
177: ExtSettingsDefaults.defaultHighlightMatchBraceDelay);
178:
179: if (highlightBrace) {
180: if (highlightBraceDelay > 0) {
181: // jdk12 compiler doesn't allow inside run()
182: final JTextComponent c2 = component;
183:
184: braceTimer = new Timer(highlightBraceDelay, null);
185: braceTimerListener = new ActionListener() {
186: public void actionPerformed(ActionEvent evt2) {
187: SwingUtilities.invokeLater(new Runnable() {
188: public void run() {
189: if (c2 != null) {
190: BaseDocument doc = Utilities
191: .getDocument(c2);
192: if (doc != null) {
193: doc.readLock();
194: try {
195: updateMatchBrace();
196: } finally {
197: doc.readUnlock();
198: }
199: }
200: }
201: }
202: });
203: }
204: };
205:
206: braceTimer.addActionListener(new WeakTimerListener(
207: braceTimerListener));
208: braceTimer.setRepeats(false);
209: } else {
210: braceTimer = null; // signal no delay
211: }
212: }
213:
214: simpleMatchBrace = SettingsUtil.getBoolean(kitClass,
215: ExtSettingsNames.CARET_SIMPLE_MATCH_BRACE,
216: ExtSettingsDefaults.defaultCaretSimpleMatchBrace);
217:
218: popupMenuEnabled = SettingsUtil.getBoolean(kitClass,
219: ExtSettingsNames.POPUP_MENU_ENABLED, true);
220: }
221: }
222:
223: public void install(JTextComponent c) {
224: EditorUI editorUI = Utilities.getEditorUI(c);
225: editorUI.addLayer(new HighlightRowLayer(),
226: HIGHLIGHT_ROW_LAYER_VISIBILITY);
227: editorUI.addLayer(new HighlightBraceLayer(),
228: HIGHLIGHT_BRACE_LAYER_VISIBILITY);
229: super .install(c);
230: }
231:
232: public void deinstall(JTextComponent c) {
233: EditorUI editorUI = Utilities.getEditorUI(c);
234: editorUI.removeLayer(HIGHLIGHT_ROW_LAYER_NAME);
235: editorUI.removeLayer(HIGHLIGHT_BRACE_LAYER_NAME);
236: super .deinstall(c);
237: }
238:
239: /**
240: * Update the matching brace of the caret. The document is read-locked while
241: * this method is called.
242: */
243: protected void updateMatchBrace() {
244: JTextComponent c = component;
245: if (c != null && highlightBrace) {
246: try {
247: EditorUI editorUI = Utilities.getEditorUI(c);
248: BaseDocument doc = (BaseDocument) c.getDocument();
249: int dotPos = getDot();
250: boolean madeValid = false; // whether brace marks display were
251: // validated
252: if (dotPos > 0) {
253: int[] matchBlk = ((ExtSyntaxSupport) doc
254: .getSyntaxSupport()).findMatchingBlock(
255: dotPos - 1, simpleMatchBrace);
256: if (matchBlk != null) {
257: if (highlightBraceStartMark != null) {
258: int markStartPos = highlightBraceStartMark
259: .getOffset();
260: int markEndPos = highlightBraceEndMark
261: .getOffset();
262: if (markStartPos != matchBlk[0]
263: || markEndPos != matchBlk[1]) {
264: editorUI.repaintBlock(markStartPos,
265: markEndPos);
266: Utilities.moveMark(doc,
267: highlightBraceStartMark,
268: matchBlk[0]);
269: Utilities.moveMark(doc,
270: highlightBraceEndMark,
271: matchBlk[1]);
272: editorUI.repaintBlock(matchBlk[0],
273: matchBlk[1]);
274: } else { // on the same position
275: if (!braceMarksValid) { // was not valid, must
276: // repaint
277: editorUI.repaintBlock(matchBlk[0],
278: matchBlk[1]);
279: }
280: }
281: } else { // highlight mark is null
282: highlightBraceStartMark = new MarkFactory.DrawMark(
283: HIGHLIGHT_BRACE_LAYER_NAME,
284: editorUI);
285: highlightBraceEndMark = new MarkFactory.DrawMark(
286: HIGHLIGHT_BRACE_LAYER_NAME,
287: editorUI);
288: highlightBraceStartMark
289: .setActivateLayer(true);
290: Utilities.insertMark(doc,
291: highlightBraceStartMark,
292: matchBlk[0]);
293: Utilities.insertMark(doc,
294: highlightBraceEndMark, matchBlk[1]);
295: editorUI.repaintBlock(matchBlk[0],
296: matchBlk[1]);
297: }
298: braceMarksValid = true;
299: madeValid = true;
300: }
301: }
302:
303: if (!madeValid) {
304: if (braceMarksValid) {
305: braceMarksValid = false;
306: editorUI.repaintBlock(highlightBraceStartMark
307: .getOffset(), highlightBraceEndMark
308: .getOffset());
309: }
310: }
311: } catch (BadLocationException e) {
312: if (System.getProperty("netbeans.debug.exceptions") != null) { // NOI18N
313: e.printStackTrace();
314: }
315: highlightBrace = false;
316: } catch (InvalidMarkException e) {
317: if (System.getProperty("netbeans.debug.exceptions") != null) { // NOI18N
318: e.printStackTrace();
319: }
320: highlightBrace = false;
321: }
322: }
323: }
324:
325: protected void update(Rectangle scrollRect, int scrollPolicy) {
326: if (highlightRow) { // highlight row with the caret
327: JTextComponent c = component;
328: if (c != null) {
329: EditorUI editorUI = Utilities.getEditorUI(c);
330: BaseDocument doc = (BaseDocument) c.getDocument();
331: int dotPos = getDot();
332: try {
333: int bolPos = Utilities.getRowStart(doc, dotPos);
334: if (highlightRowMark != null) {
335: int markPos = highlightRowMark.getOffset();
336: if (bolPos != markPos) {
337: editorUI.repaintOffset(markPos);
338: Utilities.moveMark(doc, highlightRowMark,
339: bolPos);
340: editorUI.repaintOffset(bolPos);
341: }
342: } else { // highlight mark is null
343: highlightRowMark = new MarkFactory.DrawMark(
344: HIGHLIGHT_ROW_LAYER_NAME, editorUI);
345: highlightRowMark.setActivateLayer(true);
346: Utilities.insertMark(doc, highlightRowMark,
347: bolPos);
348: editorUI.repaintOffset(bolPos);
349: }
350: } catch (BadLocationException e) {
351: highlightRow = false;
352: } catch (InvalidMarkException e) {
353: highlightRow = false;
354: }
355: }
356: }
357:
358: if (highlightBrace) {
359: if (matchBraceUpdateSync || braceTimer == null) {
360: updateMatchBrace();
361: matchBraceUpdateSync = false;
362:
363: } else { // delay the brace update
364: braceTimer.restart();
365: }
366: }
367:
368: super .update(scrollRect, scrollPolicy);
369: }
370:
371: /**
372: * Signal that the next matching brace update will be immediate without
373: * waiting for the brace timer to fire the action. This is usually done for
374: * the key-typed action.
375: */
376: public void requestMatchBraceUpdateSync() {
377: matchBraceUpdateSync = true;
378: }
379:
380: public void mousePressed(MouseEvent evt) {
381: Completion completion = ExtUtilities.getCompletion(component);
382: if (completion != null && completion.isPaneVisible()) {
383: // Hide completion if visible
384: completion.setPaneVisible(false);
385: }
386: super .mousePressed(evt);
387: }
388:
389: private static boolean isRightMouseButton(MouseEvent e) {
390: int m = e.getModifiers();
391: return (m & java.awt.event.InputEvent.BUTTON1_MASK) == 0
392: && (m & (java.awt.event.InputEvent.BUTTON2_MASK | java.awt.event.InputEvent.BUTTON3_MASK)) != 0;
393: }
394:
395: public void mouseReleased(MouseEvent evt) {
396: JTextComponent c = component;
397: if (c != null) {
398: // Show popup menu for right click
399: if (isRightMouseButton(evt) && popupMenuEnabled) {
400: ExtUtilities.getExtEditorUI(c).showPopupMenu(
401: evt.getX(), evt.getY());
402: } else {
403: super .mouseReleased(evt);
404: }
405: }
406: }
407:
408: /** Draw layer to highlight the row where the caret currently resides */
409: class HighlightRowLayer extends DrawLayerFactory.ColorLineLayer {
410:
411: public HighlightRowLayer() {
412: super (HIGHLIGHT_ROW_LAYER_NAME);
413: }
414:
415: protected Coloring getColoring(DrawContext ctx) {
416: return highlightRowColoring;
417: }
418:
419: }
420:
421: /** Draw layer to highlight the matching brace */
422: class HighlightBraceLayer extends DrawLayer.AbstractLayer {
423:
424: public HighlightBraceLayer() {
425: super (HIGHLIGHT_BRACE_LAYER_NAME);
426: }
427:
428: public void init(DrawContext ctx) {
429: }
430:
431: public boolean isActive(DrawContext ctx,
432: MarkFactory.DrawMark mark) {
433: boolean active = false;
434:
435: if (mark != null) {
436: if (braceMarksValid) {
437: active = mark.getActivateLayer();
438: }
439: }
440:
441: return active;
442: }
443:
444: public void updateContext(DrawContext ctx) {
445: if (highlightBraceColoring != null) {
446: highlightBraceColoring.apply(ctx);
447: Font f = ctx.getFont();
448: if (!f.isBold()) {
449: ctx.setFont(f.deriveFont(f.getStyle() | Font.BOLD));
450: }
451: }
452: }
453:
454: }
455:
456: }
|