1 | /*
|
---|
2 | * 08/10/2009
|
---|
3 | *
|
---|
4 | * ErrorStrip.java - A component that can visually show Parser messages (syntax
|
---|
5 | * errors, etc.) in an RSyntaxTextArea.
|
---|
6 | *
|
---|
7 | * This library is distributed under a modified BSD license. See the included
|
---|
8 | * RSyntaxTextArea.License.txt file for details.
|
---|
9 | */
|
---|
10 | package org.fife.ui.rsyntaxtextarea;
|
---|
11 |
|
---|
12 | import java.awt.Color;
|
---|
13 | import java.awt.Component;
|
---|
14 | import java.awt.Cursor;
|
---|
15 | import java.awt.Dimension;
|
---|
16 | import java.awt.Graphics;
|
---|
17 | import java.awt.Rectangle;
|
---|
18 | import java.awt.event.MouseAdapter;
|
---|
19 | import java.awt.event.MouseEvent;
|
---|
20 | import java.beans.PropertyChangeEvent;
|
---|
21 | import java.beans.PropertyChangeListener;
|
---|
22 | import java.text.MessageFormat;
|
---|
23 | import java.util.ArrayList;
|
---|
24 | import java.util.HashMap;
|
---|
25 | import java.util.Iterator;
|
---|
26 | import java.util.List;
|
---|
27 | import java.util.Map;
|
---|
28 | import java.util.ResourceBundle;
|
---|
29 | import javax.swing.JComponent;
|
---|
30 | import javax.swing.ToolTipManager;
|
---|
31 | import javax.swing.UIManager;
|
---|
32 | import javax.swing.event.CaretEvent;
|
---|
33 | import javax.swing.event.CaretListener;
|
---|
34 | import javax.swing.text.BadLocationException;
|
---|
35 |
|
---|
36 | import org.fife.ui.rsyntaxtextarea.parser.Parser;
|
---|
37 | import org.fife.ui.rsyntaxtextarea.parser.ParserNotice;
|
---|
38 | import org.fife.ui.rsyntaxtextarea.parser.TaskTagParser.TaskNotice;
|
---|
39 |
|
---|
40 |
|
---|
41 | /**
|
---|
42 | * A component to sit alongside an {@link RSyntaxTextArea} that displays
|
---|
43 | * colored markers for locations of interest (parser errors, marked
|
---|
44 | * occurrences, etc.).<p>
|
---|
45 | *
|
---|
46 | * <code>ErrorStrip</code>s display <code>ParserNotice</code>s from
|
---|
47 | * {@link Parser}s. Currently, the only way to get lines flagged in this
|
---|
48 | * component is to register a <code>Parser</code> on an RSyntaxTextArea and
|
---|
49 | * return <code>ParserNotice</code>s for each line to display an icon for.
|
---|
50 | * The severity of each notice must be at least the threshold set by
|
---|
51 | * {@link #setLevelThreshold(int)} to be displayed in this error strip. The
|
---|
52 | * default threshold is {@link ParserNotice#WARNING}.<p>
|
---|
53 | *
|
---|
54 | * An <code>ErrorStrip</code> can be added to a UI like so:
|
---|
55 | * <pre>
|
---|
56 | * textArea = createTextArea();
|
---|
57 | * textArea.addParser(new MyParser(textArea)); // Identifies lines to display
|
---|
58 | * scrollPane = new RTextScrollPane(textArea, true);
|
---|
59 | * ErrorStrip es = new ErrorStrip(textArea);
|
---|
60 | * JPanel temp = new JPanel(new BorderLayout());
|
---|
61 | * temp.add(scrollPane);
|
---|
62 | * temp.add(es, BorderLayout.LINE_END);
|
---|
63 | * </pre>
|
---|
64 | *
|
---|
65 | * @author Robert Futrell
|
---|
66 | * @version 0.5
|
---|
67 | */
|
---|
68 | /*
|
---|
69 | * Possible improvements:
|
---|
70 | * 1. Handle marked occurrence changes separately from parser changes.
|
---|
71 | * For each property change, call a method that removes the notices
|
---|
72 | * being reloaded from the Markers (removing any Markers that are now
|
---|
73 | * "empty").
|
---|
74 | * 2. When 1.4 support is dropped, replace new Integer(int) with
|
---|
75 | * Integer.valueOf(int).
|
---|
76 | */
|
---|
77 | public class ErrorStrip extends JComponent {
|
---|
78 |
|
---|
79 | /**
|
---|
80 | * The text area.
|
---|
81 | */
|
---|
82 | private RSyntaxTextArea textArea;
|
---|
83 |
|
---|
84 | /**
|
---|
85 | * Listens for events in this component.
|
---|
86 | */
|
---|
87 | private Listener listener;
|
---|
88 |
|
---|
89 | /**
|
---|
90 | * Whether "marked occurrences" in the text area should be shown in this
|
---|
91 | * error strip.
|
---|
92 | */
|
---|
93 | private boolean showMarkedOccurrences;
|
---|
94 |
|
---|
95 | /**
|
---|
96 | * Mapping of colors to brighter colors. This is kept to prevent
|
---|
97 | * unnecessary creation of the same Colors over and over.
|
---|
98 | */
|
---|
99 | private Map brighterColors;
|
---|
100 |
|
---|
101 | /**
|
---|
102 | * Only notices of this severity (or worse) will be displayed in this
|
---|
103 | * error strip.
|
---|
104 | */
|
---|
105 | private int levelThreshold;
|
---|
106 |
|
---|
107 | /**
|
---|
108 | * Whether the caret marker's location should be rendered.
|
---|
109 | */
|
---|
110 | private boolean followCaret;
|
---|
111 |
|
---|
112 | /**
|
---|
113 | * The color to use for the caret marker.
|
---|
114 | */
|
---|
115 | private Color caretMarkerColor;
|
---|
116 |
|
---|
117 | /**
|
---|
118 | * Where we paint the caret marker.
|
---|
119 | */
|
---|
120 | private int caretLineY;
|
---|
121 |
|
---|
122 | /**
|
---|
123 | * The last location of the caret marker.
|
---|
124 | */
|
---|
125 | private int lastLineY;
|
---|
126 |
|
---|
127 | /**
|
---|
128 | * The preferred width of this component.
|
---|
129 | */
|
---|
130 | private static final int PREFERRED_WIDTH = 14;
|
---|
131 |
|
---|
132 | private static final String MSG = "org.fife.ui.rsyntaxtextarea.ErrorStrip";
|
---|
133 | private static final ResourceBundle msg = ResourceBundle.getBundle(MSG);
|
---|
134 |
|
---|
135 |
|
---|
136 | /**
|
---|
137 | * Constructor.
|
---|
138 | *
|
---|
139 | * @param textArea The text area we are examining.
|
---|
140 | */
|
---|
141 | public ErrorStrip(RSyntaxTextArea textArea) {
|
---|
142 | this.textArea = textArea;
|
---|
143 | listener = new Listener();
|
---|
144 | ToolTipManager.sharedInstance().registerComponent(this);
|
---|
145 | setLayout(null); // Manually layout Markers as they can overlap
|
---|
146 | addMouseListener(listener);
|
---|
147 | setShowMarkedOccurrences(true);
|
---|
148 | setLevelThreshold(ParserNotice.WARNING);
|
---|
149 | setFollowCaret(true);
|
---|
150 | setCaretMarkerColor(Color.BLACK);
|
---|
151 | }
|
---|
152 |
|
---|
153 |
|
---|
154 | /**
|
---|
155 | * Overridden so we only start listening for parser notices when this
|
---|
156 | * component (and presumably the text area) are visible.
|
---|
157 | */
|
---|
158 | public void addNotify() {
|
---|
159 | super.addNotify();
|
---|
160 | textArea.addCaretListener(listener);
|
---|
161 | textArea.addPropertyChangeListener(
|
---|
162 | RSyntaxTextArea.PARSER_NOTICES_PROPERTY, listener);
|
---|
163 | textArea.addPropertyChangeListener(
|
---|
164 | RSyntaxTextArea.MARK_OCCURRENCES_PROPERTY, listener);
|
---|
165 | textArea.addPropertyChangeListener(
|
---|
166 | RSyntaxTextArea.MARKED_OCCURRENCES_CHANGED_PROPERTY, listener);
|
---|
167 | refreshMarkers();
|
---|
168 | }
|
---|
169 |
|
---|
170 |
|
---|
171 | /**
|
---|
172 | * Manually manages layout since this component uses no layout manager.
|
---|
173 | */
|
---|
174 | public void doLayout() {
|
---|
175 | for (int i=0; i<getComponentCount(); i++) {
|
---|
176 | Marker m = (Marker)getComponent(i);
|
---|
177 | m.updateLocation();
|
---|
178 | }
|
---|
179 | listener.caretUpdate(null); // Force recalculation of caret line pos
|
---|
180 | }
|
---|
181 |
|
---|
182 |
|
---|
183 | /**
|
---|
184 | * Returns a "brighter" color.
|
---|
185 | *
|
---|
186 | * @param c The color.
|
---|
187 | * @return A brighter color.
|
---|
188 | */
|
---|
189 | private Color getBrighterColor(Color c) {
|
---|
190 | if (brighterColors==null) {
|
---|
191 | brighterColors = new HashMap(5); // Usually small
|
---|
192 | }
|
---|
193 | Color brighter = (Color)brighterColors.get(c);
|
---|
194 | if (brighter==null) {
|
---|
195 | // Don't use c.brighter() as it doesn't work well for blue, and
|
---|
196 | // also doesn't return something brighter "enough."
|
---|
197 | int r = possiblyBrighter(c.getRed());
|
---|
198 | int g = possiblyBrighter(c.getGreen());
|
---|
199 | int b = possiblyBrighter(c.getBlue());
|
---|
200 | brighter = new Color(r, g, b);
|
---|
201 | brighterColors.put(c, brighter);
|
---|
202 | }
|
---|
203 | return brighter;
|
---|
204 | }
|
---|
205 |
|
---|
206 |
|
---|
207 | /**
|
---|
208 | * returns the color to use when painting the caret marker.
|
---|
209 | *
|
---|
210 | * @return The caret marker color.
|
---|
211 | * @see #setCaretMarkerColor(Color)
|
---|
212 | */
|
---|
213 | public Color getCaretMarkerColor() {
|
---|
214 | return caretMarkerColor;
|
---|
215 | }
|
---|
216 |
|
---|
217 |
|
---|
218 | /**
|
---|
219 | * Returns whether the caret's position should be drawn.
|
---|
220 | *
|
---|
221 | * @return Whether the caret's position should be drawn.
|
---|
222 | * @see #setFollowCaret(boolean)
|
---|
223 | */
|
---|
224 | public boolean getFollowCaret() {
|
---|
225 | return followCaret;
|
---|
226 | }
|
---|
227 |
|
---|
228 |
|
---|
229 | /**
|
---|
230 | * {@inheritDoc}
|
---|
231 | */
|
---|
232 | public Dimension getPreferredSize() {
|
---|
233 | int height = textArea.getPreferredScrollableViewportSize().height;
|
---|
234 | return new Dimension(PREFERRED_WIDTH, height);
|
---|
235 | }
|
---|
236 |
|
---|
237 |
|
---|
238 | /**
|
---|
239 | * Returns the minimum severity a parser notice must be for it to be
|
---|
240 | * displayed in this error strip. This will be one of the constants
|
---|
241 | * defined in the <code>ParserNotice</code> class.
|
---|
242 | *
|
---|
243 | * @return The minimum severity.
|
---|
244 | * @see #setLevelThreshold(int)
|
---|
245 | */
|
---|
246 | public int getLevelThreshold() {
|
---|
247 | return levelThreshold;
|
---|
248 | }
|
---|
249 |
|
---|
250 |
|
---|
251 | /**
|
---|
252 | * Returns whether marked occurrences are shown in this error strip.
|
---|
253 | *
|
---|
254 | * @return Whether marked occurrences are shown.
|
---|
255 | * @see #setShowMarkedOccurrences(boolean)
|
---|
256 | */
|
---|
257 | public boolean getShowMarkedOccurrences() {
|
---|
258 | return showMarkedOccurrences;
|
---|
259 | }
|
---|
260 |
|
---|
261 |
|
---|
262 | /**
|
---|
263 | * {@inheritDoc}
|
---|
264 | */
|
---|
265 | public String getToolTipText(MouseEvent e) {
|
---|
266 | String text = null;
|
---|
267 | int line = yToLine(e.getY());
|
---|
268 | if (line>-1) {
|
---|
269 | text = msg.getString("Line");
|
---|
270 | // TODO: 1.5: Use Integer.valueOf(line)
|
---|
271 | text = MessageFormat.format(text,
|
---|
272 | new Object[] { new Integer(line) });
|
---|
273 | }
|
---|
274 | return text;
|
---|
275 | }
|
---|
276 |
|
---|
277 |
|
---|
278 | /**
|
---|
279 | * Returns the y-offset in this component corresponding to a line in the
|
---|
280 | * text component.
|
---|
281 | *
|
---|
282 | * @param line The line.
|
---|
283 | * @return The y-offset.
|
---|
284 | * @see #yToLine(int)
|
---|
285 | */
|
---|
286 | private int lineToY(int line) {
|
---|
287 | int h = textArea.getVisibleRect().height;
|
---|
288 | float lineCount = textArea.getLineCount();
|
---|
289 | return (int)((line/lineCount) * h) - 2;
|
---|
290 | }
|
---|
291 |
|
---|
292 |
|
---|
293 | /**
|
---|
294 | * Overridden to (possibly) draw the caret's position.
|
---|
295 | *
|
---|
296 | * @param g The graphics context.
|
---|
297 | */
|
---|
298 | protected void paintComponent(Graphics g) {
|
---|
299 | super.paintComponent(g);
|
---|
300 | if (caretLineY>-1) {
|
---|
301 | g.setColor(getCaretMarkerColor());
|
---|
302 | g.fillRect(0, caretLineY, getWidth(), 2);
|
---|
303 | }
|
---|
304 | }
|
---|
305 |
|
---|
306 |
|
---|
307 | /**
|
---|
308 | * Returns a possibly brighter component for a color.
|
---|
309 | *
|
---|
310 | * @param i An RGB component for a color (0-255).
|
---|
311 | * @return A possibly brighter value for the component.
|
---|
312 | */
|
---|
313 | private static final int possiblyBrighter(int i) {
|
---|
314 | if (i<255) {
|
---|
315 | i += (int)((255-i)*0.8f);
|
---|
316 | }
|
---|
317 | return i;
|
---|
318 | }
|
---|
319 |
|
---|
320 |
|
---|
321 | /**
|
---|
322 | * Refreshes the markers displayed in this error strip.
|
---|
323 | */
|
---|
324 | private void refreshMarkers() {
|
---|
325 |
|
---|
326 | removeAll(); // listener is removed in Marker.removeNotify()
|
---|
327 | Map markerMap = new HashMap();
|
---|
328 |
|
---|
329 | List notices = textArea.getParserNotices();
|
---|
330 | for (Iterator i=notices.iterator(); i.hasNext(); ) {
|
---|
331 | ParserNotice notice = (ParserNotice)i.next();
|
---|
332 | if (notice.getLevel()<=levelThreshold ||
|
---|
333 | (notice instanceof TaskNotice)) {
|
---|
334 | // 1.5: Use Integer.valueOf(notice.getLine())
|
---|
335 | Integer key = new Integer(notice.getLine());
|
---|
336 | Marker m = (Marker)markerMap.get(key);
|
---|
337 | if (m==null) {
|
---|
338 | m = new Marker(notice);
|
---|
339 | m.addMouseListener(listener);
|
---|
340 | markerMap.put(key, m);
|
---|
341 | add(m);
|
---|
342 | }
|
---|
343 | else {
|
---|
344 | m.addNotice(notice);
|
---|
345 | }
|
---|
346 | }
|
---|
347 | }
|
---|
348 |
|
---|
349 | if (getShowMarkedOccurrences() && textArea.getMarkOccurrences()) {
|
---|
350 | List occurrences = textArea.getMarkedOccurrences();
|
---|
351 | for (Iterator i=occurrences.iterator(); i.hasNext(); ) {
|
---|
352 | DocumentRange range = (DocumentRange)i.next();
|
---|
353 | int line = 0;
|
---|
354 | try {
|
---|
355 | line = textArea.getLineOfOffset(range.getStartOffset());
|
---|
356 | } catch (BadLocationException ble) { // Never happens
|
---|
357 | continue;
|
---|
358 | }
|
---|
359 | ParserNotice notice = new MarkedOccurrenceNotice(range);
|
---|
360 | // 1.5: Use Integer.valueOf(notice.getLine())
|
---|
361 | Integer key = new Integer(line);
|
---|
362 | Marker m = (Marker)markerMap.get(key);
|
---|
363 | if (m==null) {
|
---|
364 | m = new Marker(notice);
|
---|
365 | m.addMouseListener(listener);
|
---|
366 | markerMap.put(key, m);
|
---|
367 | add(m);
|
---|
368 | }
|
---|
369 | else {
|
---|
370 | if (!m.containsMarkedOccurence()) {
|
---|
371 | m.addNotice(notice);
|
---|
372 | }
|
---|
373 | }
|
---|
374 | }
|
---|
375 | }
|
---|
376 |
|
---|
377 | revalidate();
|
---|
378 | repaint();
|
---|
379 |
|
---|
380 | }
|
---|
381 |
|
---|
382 |
|
---|
383 | /**
|
---|
384 | * {@inheritDoc}
|
---|
385 | */
|
---|
386 | public void removeNotify() {
|
---|
387 | super.removeNotify();
|
---|
388 | textArea.removeCaretListener(listener);
|
---|
389 | textArea.removePropertyChangeListener(
|
---|
390 | RSyntaxTextArea.PARSER_NOTICES_PROPERTY, listener);
|
---|
391 | textArea.removePropertyChangeListener(
|
---|
392 | RSyntaxTextArea.MARK_OCCURRENCES_PROPERTY, listener);
|
---|
393 | textArea.removePropertyChangeListener(
|
---|
394 | RSyntaxTextArea.MARKED_OCCURRENCES_CHANGED_PROPERTY, listener);
|
---|
395 | }
|
---|
396 |
|
---|
397 |
|
---|
398 | /**
|
---|
399 | * Sets the color to use when painting the caret marker.
|
---|
400 | *
|
---|
401 | * @param color The new caret marker color.
|
---|
402 | * @see #getCaretMarkerColor()
|
---|
403 | */
|
---|
404 | public void setCaretMarkerColor(Color color) {
|
---|
405 | if (color!=null) {
|
---|
406 | caretMarkerColor = color;
|
---|
407 | listener.caretUpdate(null); // Force repaint
|
---|
408 | }
|
---|
409 | }
|
---|
410 |
|
---|
411 |
|
---|
412 | /**
|
---|
413 | * Toggles whether the caret's current location should be drawn.
|
---|
414 | *
|
---|
415 | * @param follow Whether the caret's current location should be followed.
|
---|
416 | * @see #getFollowCaret()
|
---|
417 | */
|
---|
418 | public void setFollowCaret(boolean follow) {
|
---|
419 | if (followCaret!=follow) {
|
---|
420 | if (followCaret) {
|
---|
421 | repaint(0,caretLineY, getWidth(),2); // Erase
|
---|
422 | }
|
---|
423 | caretLineY = -1;
|
---|
424 | lastLineY = -1;
|
---|
425 | followCaret = follow;
|
---|
426 | listener.caretUpdate(null); // Possibly repaint
|
---|
427 | }
|
---|
428 | }
|
---|
429 |
|
---|
430 |
|
---|
431 | /**
|
---|
432 | * Sets the minimum severity a parser notice must be for it to be displayed
|
---|
433 | * in this error strip. This should be one of the constants defined in
|
---|
434 | * the <code>ParserNotice</code> class. The default value is
|
---|
435 | * {@link ParserNotice#WARNING}.
|
---|
436 | *
|
---|
437 | * @param level The new severity threshold.
|
---|
438 | * @see #getLevelThreshold()
|
---|
439 | * @see ParserNotice
|
---|
440 | */
|
---|
441 | public void setLevelThreshold(int level) {
|
---|
442 | levelThreshold = level;
|
---|
443 | if (isDisplayable()) {
|
---|
444 | refreshMarkers();
|
---|
445 | }
|
---|
446 | }
|
---|
447 |
|
---|
448 |
|
---|
449 | /**
|
---|
450 | * Sets whether marked occurrences are shown in this error strip.
|
---|
451 | *
|
---|
452 | * @param show Whether to show marked occurrences.
|
---|
453 | * @see #getShowMarkedOccurrences()
|
---|
454 | */
|
---|
455 | public void setShowMarkedOccurrences(boolean show) {
|
---|
456 | if (show!=showMarkedOccurrences) {
|
---|
457 | showMarkedOccurrences = show;
|
---|
458 | if (isDisplayable()) { // Skip this when we're first created
|
---|
459 | refreshMarkers();
|
---|
460 | }
|
---|
461 | }
|
---|
462 | }
|
---|
463 |
|
---|
464 |
|
---|
465 | /**
|
---|
466 | * Returns the line in the text area corresponding to a y-offset in this
|
---|
467 | * component.
|
---|
468 | *
|
---|
469 | * @param y The y-offset.
|
---|
470 | * @return The line.
|
---|
471 | * @see #lineToY(int)
|
---|
472 | */
|
---|
473 | private final int yToLine(int y) {
|
---|
474 | int line = -1;
|
---|
475 | int h = textArea.getVisibleRect().height;
|
---|
476 | if (y<h) {
|
---|
477 | float at = y/(float)h;
|
---|
478 | line = (int)(textArea.getLineCount()*at);
|
---|
479 | }
|
---|
480 | return line;
|
---|
481 | }
|
---|
482 |
|
---|
483 |
|
---|
484 | /**
|
---|
485 | * Listens for events in the error strip and its markers.
|
---|
486 | */
|
---|
487 | private class Listener extends MouseAdapter
|
---|
488 | implements PropertyChangeListener, CaretListener {
|
---|
489 |
|
---|
490 | private Rectangle visibleRect = new Rectangle();
|
---|
491 |
|
---|
492 | public void caretUpdate(CaretEvent e) {
|
---|
493 | if (getFollowCaret()) {
|
---|
494 | int line = textArea.getCaretLineNumber();
|
---|
495 | float percent = line / ((float)textArea.getLineCount());
|
---|
496 | textArea.computeVisibleRect(visibleRect);
|
---|
497 | caretLineY = (int)(visibleRect.height*percent);
|
---|
498 | if (caretLineY!=lastLineY) {
|
---|
499 | repaint(0,lastLineY, getWidth(), 2); // Erase old position
|
---|
500 | repaint(0,caretLineY, getWidth(), 2);
|
---|
501 | lastLineY = caretLineY;
|
---|
502 | }
|
---|
503 | }
|
---|
504 | }
|
---|
505 |
|
---|
506 | public void mouseClicked(MouseEvent e) {
|
---|
507 |
|
---|
508 | Component source = (Component)e.getSource();
|
---|
509 | if (source instanceof Marker) {
|
---|
510 | ((Marker)source).mouseClicked(e);
|
---|
511 | return;
|
---|
512 | }
|
---|
513 |
|
---|
514 | int line = yToLine(e.getY());
|
---|
515 | if (line>-1) {
|
---|
516 | try {
|
---|
517 | int offs = textArea.getLineStartOffset(line);
|
---|
518 | textArea.setCaretPosition(offs);
|
---|
519 | } catch (BadLocationException ble) { // Never happens
|
---|
520 | UIManager.getLookAndFeel().provideErrorFeedback(textArea);
|
---|
521 | }
|
---|
522 | }
|
---|
523 |
|
---|
524 | }
|
---|
525 |
|
---|
526 | public void propertyChange(PropertyChangeEvent e) {
|
---|
527 |
|
---|
528 | String propName = e.getPropertyName();
|
---|
529 |
|
---|
530 | // If they change whether marked occurrences are visible in editor
|
---|
531 | if (RSyntaxTextArea.MARK_OCCURRENCES_PROPERTY.equals(propName)) {
|
---|
532 | if (getShowMarkedOccurrences()) {
|
---|
533 | refreshMarkers();
|
---|
534 | }
|
---|
535 | }
|
---|
536 |
|
---|
537 | // If parser notices changed.
|
---|
538 | else if (RSyntaxTextArea.PARSER_NOTICES_PROPERTY.equals(propName)) {
|
---|
539 | refreshMarkers();
|
---|
540 | }
|
---|
541 |
|
---|
542 | // If marked occurrences changed.
|
---|
543 | else if (RSyntaxTextArea.MARKED_OCCURRENCES_CHANGED_PROPERTY.
|
---|
544 | equals(propName)) {
|
---|
545 | if (getShowMarkedOccurrences()) {
|
---|
546 | refreshMarkers();
|
---|
547 | }
|
---|
548 | }
|
---|
549 |
|
---|
550 | }
|
---|
551 |
|
---|
552 | }
|
---|
553 |
|
---|
554 |
|
---|
555 | private static final Color COLOR = new Color(220, 220, 220);
|
---|
556 | /**
|
---|
557 | * A notice that wraps a "marked occurrence."
|
---|
558 | */
|
---|
559 | private class MarkedOccurrenceNotice implements ParserNotice {
|
---|
560 |
|
---|
561 | private DocumentRange range;
|
---|
562 |
|
---|
563 | public MarkedOccurrenceNotice(DocumentRange range) {
|
---|
564 | this.range = range;
|
---|
565 | }
|
---|
566 |
|
---|
567 | public int compareTo(Object o) {
|
---|
568 | return 0; // Value doesn't matter
|
---|
569 | }
|
---|
570 |
|
---|
571 | public boolean containsPosition(int pos) {
|
---|
572 | return pos>=range.getStartOffset() && pos<range.getEndOffset();
|
---|
573 | }
|
---|
574 |
|
---|
575 | public boolean equals(Object o) {
|
---|
576 | // FindBugs - Define equals() when defining compareTo()
|
---|
577 | return compareTo(o)==0;
|
---|
578 | }
|
---|
579 |
|
---|
580 | public Color getColor() {
|
---|
581 | return COLOR;
|
---|
582 | //return textArea.getMarkOccurrencesColor();
|
---|
583 | }
|
---|
584 |
|
---|
585 | public int getLength() {
|
---|
586 | return range.getEndOffset() - range.getStartOffset();
|
---|
587 | }
|
---|
588 |
|
---|
589 | public int getLevel() {
|
---|
590 | return INFO; // Won't matter
|
---|
591 | }
|
---|
592 |
|
---|
593 | public int getLine() {
|
---|
594 | try {
|
---|
595 | return textArea.getLineOfOffset(range.getStartOffset());
|
---|
596 | } catch (BadLocationException ble) {
|
---|
597 | return 0;
|
---|
598 | }
|
---|
599 | }
|
---|
600 |
|
---|
601 | public String getMessage() {
|
---|
602 | String text = null;
|
---|
603 | try {
|
---|
604 | String word = textArea.getText(range.getStartOffset(),
|
---|
605 | getLength());
|
---|
606 | text = msg.getString("OccurrenceOf");
|
---|
607 | text = MessageFormat.format(text, new String[] { word });
|
---|
608 | } catch (BadLocationException ble) {
|
---|
609 | UIManager.getLookAndFeel().provideErrorFeedback(textArea);
|
---|
610 | }
|
---|
611 | return text;
|
---|
612 | }
|
---|
613 |
|
---|
614 | public int getOffset() {
|
---|
615 | return range.getStartOffset();
|
---|
616 | }
|
---|
617 |
|
---|
618 | public Parser getParser() {
|
---|
619 | return null;
|
---|
620 | }
|
---|
621 |
|
---|
622 | public boolean getShowInEditor() {
|
---|
623 | return false; // Value doesn't matter
|
---|
624 | }
|
---|
625 |
|
---|
626 | public String getToolTipText() {
|
---|
627 | return null;
|
---|
628 | }
|
---|
629 |
|
---|
630 | public int hashCode() { // FindBugs, since we override equals()
|
---|
631 | return 0; // Value doesn't matter for us.
|
---|
632 | }
|
---|
633 |
|
---|
634 | }
|
---|
635 |
|
---|
636 |
|
---|
637 | /**
|
---|
638 | * A "marker" in this error strip, representing one or more notices.
|
---|
639 | */
|
---|
640 | private class Marker extends JComponent {
|
---|
641 |
|
---|
642 | private List notices;
|
---|
643 |
|
---|
644 | public Marker(ParserNotice notice) {
|
---|
645 | notices = new ArrayList(1); // Usually just 1
|
---|
646 | addNotice(notice);
|
---|
647 | setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
---|
648 | setSize(getPreferredSize());
|
---|
649 | ToolTipManager.sharedInstance().registerComponent(this);
|
---|
650 | }
|
---|
651 |
|
---|
652 | public void addNotice(ParserNotice notice) {
|
---|
653 | notices.add(notice);
|
---|
654 | }
|
---|
655 |
|
---|
656 | public boolean containsMarkedOccurence() {
|
---|
657 | boolean result = false;
|
---|
658 | for (int i=0; i<notices.size(); i++) {
|
---|
659 | if (notices.get(i) instanceof MarkedOccurrenceNotice) {
|
---|
660 | result = true;
|
---|
661 | break;
|
---|
662 | }
|
---|
663 | }
|
---|
664 | return result;
|
---|
665 | }
|
---|
666 |
|
---|
667 | public Color getColor() {
|
---|
668 | // Return the color for the highest-level parser.
|
---|
669 | Color c = null;
|
---|
670 | int lowestLevel = Integer.MAX_VALUE; // ERROR is 0
|
---|
671 | for (Iterator i=notices.iterator(); i.hasNext(); ) {
|
---|
672 | ParserNotice notice = (ParserNotice)i.next();
|
---|
673 | if (notice.getLevel()<lowestLevel) {
|
---|
674 | lowestLevel = notice.getLevel();
|
---|
675 | c = notice.getColor();
|
---|
676 | }
|
---|
677 | }
|
---|
678 | return c;
|
---|
679 | }
|
---|
680 |
|
---|
681 | public Dimension getPreferredSize() {
|
---|
682 | int w = PREFERRED_WIDTH - 4; // 2-pixel empty border
|
---|
683 | return new Dimension(w, 5);
|
---|
684 | }
|
---|
685 |
|
---|
686 | public String getToolTipText() {
|
---|
687 |
|
---|
688 | String text = null;
|
---|
689 |
|
---|
690 | if (notices.size()==1) {
|
---|
691 | text = ((ParserNotice)notices.get(0)).getMessage();
|
---|
692 | }
|
---|
693 | else { // > 1
|
---|
694 | StringBuffer sb = new StringBuffer("<html>");
|
---|
695 | sb.append(msg.getString("MultipleMarkers"));
|
---|
696 | sb.append("<br>");
|
---|
697 | for (int i=0; i<notices.size(); i++) {
|
---|
698 | ParserNotice pn = (ParserNotice)notices.get(i);
|
---|
699 | sb.append(" - ");
|
---|
700 | sb.append(pn.getMessage());
|
---|
701 | sb.append("<br>");
|
---|
702 | }
|
---|
703 | text = sb.toString();
|
---|
704 | }
|
---|
705 |
|
---|
706 | return text;
|
---|
707 |
|
---|
708 | }
|
---|
709 |
|
---|
710 | protected void mouseClicked(MouseEvent e) {
|
---|
711 | ParserNotice pn = (ParserNotice)notices.get(0);
|
---|
712 | int offs = pn.getOffset();
|
---|
713 | int len = pn.getLength();
|
---|
714 | if (offs>-1 && len>-1) { // These values are optional
|
---|
715 | textArea.setSelectionStart(offs);
|
---|
716 | textArea.setSelectionEnd(offs+len);
|
---|
717 | }
|
---|
718 | else {
|
---|
719 | int line = pn.getLine();
|
---|
720 | try {
|
---|
721 | offs = textArea.getLineStartOffset(line);
|
---|
722 | textArea.setCaretPosition(offs);
|
---|
723 | } catch (BadLocationException ble) { // Never happens
|
---|
724 | UIManager.getLookAndFeel().provideErrorFeedback(textArea);
|
---|
725 | }
|
---|
726 | }
|
---|
727 | }
|
---|
728 |
|
---|
729 | protected void paintComponent(Graphics g) {
|
---|
730 |
|
---|
731 | // TODO: Give "priorities" and always pick color of a notice with
|
---|
732 | // highest priority (e.g. parsing errors will usually be red).
|
---|
733 |
|
---|
734 | Color borderColor = getColor();
|
---|
735 | if (borderColor==null) {
|
---|
736 | borderColor = Color.DARK_GRAY;
|
---|
737 | }
|
---|
738 | Color fillColor = getBrighterColor(borderColor);
|
---|
739 |
|
---|
740 | int w = getWidth();
|
---|
741 | int h = getHeight();
|
---|
742 |
|
---|
743 | g.setColor(fillColor);
|
---|
744 | g.fillRect(0,0, w,h);
|
---|
745 |
|
---|
746 | g.setColor(borderColor);
|
---|
747 | g.drawRect(0,0, w-1,h-1);
|
---|
748 |
|
---|
749 | }
|
---|
750 |
|
---|
751 | public void removeNotify() {
|
---|
752 | super.removeNotify();
|
---|
753 | ToolTipManager.sharedInstance().unregisterComponent(this);
|
---|
754 | removeMouseListener(listener);
|
---|
755 | }
|
---|
756 |
|
---|
757 | public void updateLocation() {
|
---|
758 | int line = ((ParserNotice)notices.get(0)).getLine();
|
---|
759 | int y = lineToY(line);
|
---|
760 | setLocation(2, y);
|
---|
761 | }
|
---|
762 |
|
---|
763 | }
|
---|
764 |
|
---|
765 |
|
---|
766 | } |
---|