001: /*BEGIN_COPYRIGHT_BLOCK
002: *
003: * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions are met:
008: * * Redistributions of source code must retain the above copyright
009: * notice, this list of conditions and the following disclaimer.
010: * * Redistributions in binary form must reproduce the above copyright
011: * notice, this list of conditions and the following disclaimer in the
012: * documentation and/or other materials provided with the distribution.
013: * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
014: * names of its contributors may be used to endorse or promote products
015: * derived from this software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028: *
029: * This software is Open Source Initiative approved Open Source Software.
030: * Open Source Initative Approved is a trademark of the Open Source Initiative.
031: *
032: * This file is part of DrJava. Download the current version of this project
033: * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
034: *
035: * END_COPYRIGHT_BLOCK*/
036:
037: package edu.rice.cs.drjava.model.definitions;
038:
039: import javax.swing.text.*;
040: import java.awt.*;
041: import javax.swing.event.DocumentEvent; // TODO: Check synchronization.
042: import java.util.Vector;
043:
044: import edu.rice.cs.drjava.DrJava;
045: import edu.rice.cs.drjava.model.*;
046: import edu.rice.cs.drjava.model.repl.InteractionsDJDocument;
047: import edu.rice.cs.drjava.config.OptionConstants;
048: import edu.rice.cs.drjava.config.OptionEvent;
049: import edu.rice.cs.drjava.config.OptionListener;
050: import edu.rice.cs.drjava.model.definitions.reducedmodel.*;
051:
052: public class ColoringGlyphPainter extends GlyphView.GlyphPainter
053: implements OptionConstants {
054:
055: public static Color COMMENTED_COLOR = DrJava.getConfig()
056: .getSetting(DEFINITIONS_COMMENT_COLOR);
057: public static Color DOUBLE_QUOTED_COLOR = DrJava.getConfig()
058: .getSetting(DEFINITIONS_DOUBLE_QUOTED_COLOR);
059: public static Color SINGLE_QUOTED_COLOR = DrJava.getConfig()
060: .getSetting(DEFINITIONS_SINGLE_QUOTED_COLOR);
061: public static Color NORMAL_COLOR = DrJava.getConfig().getSetting(
062: DEFINITIONS_NORMAL_COLOR);
063: public static Color KEYWORD_COLOR = DrJava.getConfig().getSetting(
064: DEFINITIONS_KEYWORD_COLOR);
065: public static Color NUMBER_COLOR = DrJava.getConfig().getSetting(
066: DEFINITIONS_NUMBER_COLOR);
067: public static Color TYPE_COLOR = DrJava.getConfig().getSetting(
068: DEFINITIONS_TYPE_COLOR);
069: public static Font MAIN_FONT = DrJava.getConfig().getSetting(
070: FONT_MAIN);
071:
072: //Interactions only colors
073: public static Color INTERACTIONS_SYSTEM_ERR_COLOR = DrJava
074: .getConfig().getSetting(SYSTEM_ERR_COLOR);
075: public static Color INTERACTIONS_SYSTEM_IN_COLOR = DrJava
076: .getConfig().getSetting(SYSTEM_IN_COLOR);
077: public static Color INTERACTIONS_SYSTEM_OUT_COLOR = DrJava
078: .getConfig().getSetting(SYSTEM_OUT_COLOR);
079: //Renamed as to avoid confusion with the one in option constants
080: public static Color ERROR_COLOR = DrJava.getConfig().getSetting(
081: INTERACTIONS_ERROR_COLOR);
082: public static Color DEBUGGER_COLOR = DrJava.getConfig().getSetting(
083: DEBUG_MESSAGE_COLOR);
084:
085: private boolean _listenersAttached;
086: private Runnable _lambdaRepaint;
087: private FontMetrics _metrics;
088:
089: public ColoringGlyphPainter(Runnable lambdaRepaint) {
090: _listenersAttached = false;
091: _lambdaRepaint = lambdaRepaint;
092: // _metrics is initialized by sync(), which thus must be called before any use of _metrics
093: }
094:
095: /** Paints the glyphs representing the given range. */
096: public void paint(GlyphView v, Graphics g, Shape a, int start,
097: int end) {
098:
099: // If there's nothing to show, don't do anything!
100: // For some reason I don't understand we tend to get called sometimes to render a zero-length area.
101: if (start == end)
102: return;
103:
104: sync(v);
105:
106: // Might be a PlainDocument (when AbstractDJPane is first constructed).
107: // See comments for DefinitionsEditorKit.createNewDocument() for details.
108: Document doc = v.getDocument();
109: AbstractDJDocument djdoc = null;
110: if (doc instanceof AbstractDJDocument)
111: djdoc = (AbstractDJDocument) doc;
112: else
113: return; // return if there is no AbstracDJDocument
114:
115: Segment text;
116: TabExpander expander = v.getTabExpander();
117: Rectangle alloc = (a instanceof Rectangle) ? (Rectangle) a : a
118: .getBounds();
119:
120: // determine the x coordinate to render the glyphs
121: int x = alloc.x;
122: int p = v.getStartOffset();
123: if (p != start) {
124: text = v.getText(p, start);
125: int width = Utilities.getTabbedTextWidth(text, _metrics, x,
126: expander, p);
127: x += width;
128: }
129:
130: // determine the y coordinate to render the glyphs
131: int y = alloc.y + _metrics.getHeight() - _metrics.getDescent();
132:
133: text = v.getText(start, end);
134:
135: Vector<HighlightStatus> stats = djdoc.getHighlightStatus(start,
136: end);
137: if (stats.size() < 1)
138: throw new RuntimeException(
139: "GetHighlightStatus returned nothing!");
140: try {
141: for (int i = 0; i < stats.size(); i++) {
142: HighlightStatus stat = stats.get(i);
143: int length = stat.getLength();
144: int location = stat.getLocation();
145:
146: if ((location < end) && ((location + length) > start)) {
147:
148: // Adjust the length and location to fit within the bounds of
149: // the element we're about to render
150: if (location < start) {
151: length -= (start - location);
152: location = start;
153: }
154: if ((location + length) > end) {
155: length = end - location;
156: }
157:
158: if (!(djdoc instanceof InteractionsDJDocument)
159: || !((InteractionsDJDocument) djdoc)
160: .setColoring((start + end) / 2, g))
161: setFormattingForState(g, stat.getState());
162:
163: djdoc.getText(location, length, text);
164: x = Utilities.drawTabbedText(text, x, y, g, v
165: .getTabExpander(), location);
166: }
167: }
168: } catch (BadLocationException ble) {
169: // don't continue rendering if such an exception is found
170: }
171: }
172:
173: /**
174: * Determine the span the glyphs given a start location
175: * (for tab expansion).
176: */
177: public float getSpan(GlyphView v, int start, int end,
178: TabExpander e, float x) {
179: sync(v);
180: Segment text = v.getText(start, end);
181: int width = Utilities.getTabbedTextWidth(text, _metrics,
182: (int) x, e, start);
183: return width;
184: }
185:
186: public float getHeight(GlyphView v) {
187: sync(v);
188: return _metrics.getHeight();
189: }
190:
191: /**
192: * Fetches the ascent above the baseline for the glyphs
193: * corresponding to the given range in the model.
194: */
195: public float getAscent(GlyphView v) {
196: sync(v);
197: return _metrics.getAscent();
198: }
199:
200: /**
201: * Fetches the descent below the baseline for the glyphs
202: * corresponding to the given range in the model.
203: */
204: public float getDescent(GlyphView v) {
205: sync(v);
206: return _metrics.getDescent();
207: }
208:
209: public Shape modelToView(GlyphView v, int pos, Position.Bias bias,
210: Shape a) throws BadLocationException {
211:
212: sync(v);
213: Rectangle alloc = (a instanceof Rectangle) ? (Rectangle) a : a
214: .getBounds();
215: int start = v.getStartOffset();
216: int end = v.getEndOffset();
217: TabExpander expander = v.getTabExpander();
218: Segment text;
219:
220: if (pos == end) {
221: // The caller of this is left to right and borders a right to
222: // left view, return our end location.
223: return new Rectangle(alloc.x + alloc.width, alloc.y, 0,
224: _metrics.getHeight());
225: }
226: if ((pos >= start) && (pos <= end)) {
227: // determine range to the left of the position
228: text = v.getText(start, pos);
229: int width = Utilities.getTabbedTextWidth(text, _metrics,
230: alloc.x, expander, start);
231: return new Rectangle(alloc.x + width, alloc.y, 0, _metrics
232: .getHeight());
233: }
234: throw new BadLocationException("modelToView - can't convert",
235: end);
236: }
237:
238: /**
239: * Provides a mapping from the view coordinate space to the logical
240: * coordinate space of the model.
241: *
242: * @param v the view containing the view coordinates
243: * @param x the X coordinate
244: * @param y the Y coordinate
245: * @param a the allocated region to render into
246: * @param biasReturn always returns <code>Position.Bias.Forward</code>
247: * as the zero-th element of this array
248: * @return the location within the model that best represents the
249: * given point in the view
250: * @see View#viewToModel
251: */
252: public int viewToModel(GlyphView v, float x, float y, Shape a,
253: Position.Bias[] biasReturn) {
254: sync(v);
255: Rectangle alloc = (a instanceof Rectangle) ? (Rectangle) a : a
256: .getBounds();
257: int start = v.getStartOffset();
258: int end = v.getEndOffset();
259: TabExpander expander = v.getTabExpander();
260: Segment text = v.getText(start, end);
261:
262: int offs = Utilities.getTabbedTextOffset(text, _metrics,
263: alloc.x, (int) x, expander, start);
264: int retValue = start + offs;
265: if (retValue == end) {
266: // No need to return backward bias as GlyphPainter1 is used for
267: // ltr text only.
268: retValue--;
269: }
270: biasReturn[0] = Position.Bias.Forward;
271: return retValue;
272: }
273:
274: /**
275: * Determines the best location (in the model) to break the given view. This method attempts to break on a
276: * whitespace location. If a whitespace location can't be found, the nearest character location is returned.
277: *
278: * @param v The view
279: * @param start The location in the model where the fragment should start its representation >= 0
280: * @param x The graphic location along the axis that the broken view would occupy >= 0; this may be useful for
281: * things like tab calculations
282: * @param len Specifies the distance into the view where a potential break is desired >= 0
283: * @return The model location desired for a break
284: * @see View#breakView
285: */
286: public int getBoundedPosition(GlyphView v, int start, float x,
287: float len) {
288: sync(v);
289: TabExpander expander = v.getTabExpander();
290: Segment s = v.getText(start, v.getEndOffset());
291: int index = Utilities.getTabbedTextOffset(s, _metrics, (int) x,
292: (int) (x + len), expander, start, false);
293: int end = start + index;
294: return end;
295: }
296:
297: void sync(GlyphView v) {
298: Font f = v.getFont();
299: if ((_metrics == null) || (!f.equals(_metrics.getFont()))) {
300: // fetch a new FontMetrics
301: Toolkit kit;
302: Component c = v.getContainer();
303: if (c != null) {
304: kit = c.getToolkit();
305: } else {
306: kit = Toolkit.getDefaultToolkit();
307: }
308: /* The deprecated method here is necessary to get a handle on a FontMetrics object. This is required by our
309: * dependence on the javax.swing.text.Utilities class, which does a lot of Java 1.1-style calculation (presumably
310: * these methods should be deprecated, too). The deprecated use can't be fixed without an in-depth understanding
311: * of fonts, glyphs, and font rendering. Where _metrics is currently used, the Font methods getLineMetrics,
312: * getStringBounds, getHeight, getAscent, and getDescent will probably be helpful.
313: */
314: @SuppressWarnings("deprecation")
315: FontMetrics newMetrics = kit.getFontMetrics(f);
316: _metrics = newMetrics;
317: }
318:
319: Document doc = v.getDocument();
320: if (!_listenersAttached && (doc instanceof AbstractDJDocument)) {
321: attachOptionListeners((AbstractDJDocument) doc);
322: }
323: }
324:
325: /** Given a particular state, assign it a color.
326: * @param g Graphics object
327: * @param state a given state
328: */
329: private void setFormattingForState(Graphics g, int state) {
330: switch (state) {
331: case HighlightStatus.NORMAL:
332: g.setColor(NORMAL_COLOR);
333: break;
334: case HighlightStatus.COMMENTED:
335: g.setColor(COMMENTED_COLOR);
336: break;
337: case HighlightStatus.SINGLE_QUOTED:
338: g.setColor(SINGLE_QUOTED_COLOR);
339: break;
340: case HighlightStatus.DOUBLE_QUOTED:
341: g.setColor(DOUBLE_QUOTED_COLOR);
342: break;
343: case HighlightStatus.KEYWORD:
344: g.setColor(KEYWORD_COLOR);
345: break;
346: case HighlightStatus.NUMBER:
347: g.setColor(NUMBER_COLOR);
348: break;
349: case HighlightStatus.TYPE:
350: g.setColor(TYPE_COLOR);
351: break;
352: default:
353: throw new RuntimeException(
354: "Can't get color for invalid state: " + state);
355: }
356: g.setFont(MAIN_FONT);
357: }
358:
359: /** Called when a change occurs.
360: * @param changes document changes
361: * @param a a Shape
362: * @param f a ViewFactory
363: */
364: // public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
365: // super.changedUpdate(changes, a, f);
366: // // Make sure we redraw since something changed in the formatting
367: // Container c = getContainer();
368: // if (c != null) c.repaint();
369: // }
370: private void attachOptionListeners(AbstractDJDocument doc) {
371: // Listen for updates to configurable colors
372: final ColorOptionListener col = new ColorOptionListener();
373: final FontOptionListener fol = new FontOptionListener();
374:
375: // delete the old color listeners, because they're hanging onto the wrong coloringview
376: // add color listeners to highlight keywords etc
377: DrJava.getConfig().addOptionListener(
378: OptionConstants.DEFINITIONS_COMMENT_COLOR, col);
379: DrJava.getConfig().addOptionListener(
380: OptionConstants.DEFINITIONS_DOUBLE_QUOTED_COLOR, col);
381: DrJava.getConfig().addOptionListener(
382: OptionConstants.DEFINITIONS_SINGLE_QUOTED_COLOR, col);
383: DrJava.getConfig().addOptionListener(
384: OptionConstants.DEFINITIONS_NORMAL_COLOR, col);
385: DrJava.getConfig().addOptionListener(
386: OptionConstants.DEFINITIONS_KEYWORD_COLOR, col);
387: DrJava.getConfig().addOptionListener(
388: OptionConstants.DEFINITIONS_NUMBER_COLOR, col);
389: DrJava.getConfig().addOptionListener(
390: OptionConstants.DEFINITIONS_TYPE_COLOR, col);
391: DrJava.getConfig().addOptionListener(OptionConstants.FONT_MAIN,
392: fol);
393:
394: DrJava.getConfig().addOptionListener(
395: OptionConstants.SYSTEM_ERR_COLOR, col);
396: DrJava.getConfig().addOptionListener(
397: OptionConstants.SYSTEM_IN_COLOR, col);
398: DrJava.getConfig().addOptionListener(
399: OptionConstants.SYSTEM_OUT_COLOR, col);
400: DrJava.getConfig().addOptionListener(
401: OptionConstants.INTERACTIONS_ERROR_COLOR, col);
402: DrJava.getConfig().addOptionListener(
403: OptionConstants.DEBUG_MESSAGE_COLOR, col);
404:
405: // The listeners that were added in the above lines need to be removed from
406: // the config framework when the document corresponding to this painter is
407: // kicked out of the DocumentCache. Otherwise, this painter will remain
408: // un-garbage-collected and unused.
409: if (doc instanceof DefinitionsDocument) {
410: // remove the listeners when the document closes
411: ((DefinitionsDocument) doc)
412: .addDocumentClosedListener(new DocumentClosedListener() {
413: public void close() {
414: DrJava
415: .getConfig()
416: .removeOptionListener(
417: OptionConstants.DEFINITIONS_COMMENT_COLOR,
418: col);
419: DrJava
420: .getConfig()
421: .removeOptionListener(
422: OptionConstants.DEFINITIONS_DOUBLE_QUOTED_COLOR,
423: col);
424: DrJava
425: .getConfig()
426: .removeOptionListener(
427: OptionConstants.DEFINITIONS_SINGLE_QUOTED_COLOR,
428: col);
429: DrJava
430: .getConfig()
431: .removeOptionListener(
432: OptionConstants.DEFINITIONS_NORMAL_COLOR,
433: col);
434: DrJava
435: .getConfig()
436: .removeOptionListener(
437: OptionConstants.DEFINITIONS_KEYWORD_COLOR,
438: col);
439: DrJava
440: .getConfig()
441: .removeOptionListener(
442: OptionConstants.DEFINITIONS_NUMBER_COLOR,
443: col);
444: DrJava
445: .getConfig()
446: .removeOptionListener(
447: OptionConstants.DEFINITIONS_TYPE_COLOR,
448: col);
449: DrJava.getConfig().removeOptionListener(
450: OptionConstants.FONT_MAIN, fol);
451: DrJava.getConfig().removeOptionListener(
452: OptionConstants.SYSTEM_ERR_COLOR,
453: col);
454: DrJava.getConfig().removeOptionListener(
455: OptionConstants.SYSTEM_IN_COLOR,
456: col);
457: DrJava.getConfig().removeOptionListener(
458: OptionConstants.SYSTEM_OUT_COLOR,
459: col);
460: DrJava
461: .getConfig()
462: .removeOptionListener(
463: OptionConstants.INTERACTIONS_ERROR_COLOR,
464: col);
465: DrJava
466: .getConfig()
467: .removeOptionListener(
468: OptionConstants.DEBUG_MESSAGE_COLOR,
469: col);
470: }
471: });
472: }
473: _listenersAttached = true;
474: }
475:
476: /** Called when an OptionListener perceives a change in any of the colors */
477: public void updateColors() {
478:
479: COMMENTED_COLOR = DrJava.getConfig().getSetting(
480: DEFINITIONS_COMMENT_COLOR);
481: DOUBLE_QUOTED_COLOR = DrJava.getConfig().getSetting(
482: DEFINITIONS_DOUBLE_QUOTED_COLOR);
483: SINGLE_QUOTED_COLOR = DrJava.getConfig().getSetting(
484: DEFINITIONS_SINGLE_QUOTED_COLOR);
485: NORMAL_COLOR = DrJava.getConfig().getSetting(
486: DEFINITIONS_NORMAL_COLOR);
487: KEYWORD_COLOR = DrJava.getConfig().getSetting(
488: DEFINITIONS_KEYWORD_COLOR);
489: NUMBER_COLOR = DrJava.getConfig().getSetting(
490: DEFINITIONS_NUMBER_COLOR);
491: TYPE_COLOR = DrJava.getConfig().getSetting(
492: DEFINITIONS_TYPE_COLOR);
493:
494: INTERACTIONS_SYSTEM_ERR_COLOR = DrJava.getConfig().getSetting(
495: SYSTEM_ERR_COLOR);
496: INTERACTIONS_SYSTEM_IN_COLOR = DrJava.getConfig().getSetting(
497: SYSTEM_IN_COLOR);
498: INTERACTIONS_SYSTEM_OUT_COLOR = DrJava.getConfig().getSetting(
499: SYSTEM_OUT_COLOR);
500: ERROR_COLOR = DrJava.getConfig().getSetting(
501: INTERACTIONS_ERROR_COLOR);
502: DEBUGGER_COLOR = DrJava.getConfig().getSetting(
503: DEBUG_MESSAGE_COLOR);
504:
505: edu.rice.cs.util.swing.Utilities.invokeLater(_lambdaRepaint);
506: }
507:
508: /** The OptionListeners for DEFINITIONS COLORs */
509: private class ColorOptionListener implements OptionListener<Color> {
510: public void optionChanged(OptionEvent<Color> oce) {
511: updateColors();
512: }
513: }
514:
515: private static class FontOptionListener implements
516: OptionListener<Font> {
517: public void optionChanged(OptionEvent<Font> oce) {
518: MAIN_FONT = DrJava.getConfig().getSetting(FONT_MAIN);
519: }
520: }
521:
522: }
|