001/* DefaultHighlighter.java -- The default highlight for Swing
002   Copyright (C) 2004, 2006  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing.text;
040
041import java.awt.Color;
042import java.awt.Graphics;
043import java.awt.Insets;
044import java.awt.Rectangle;
045import java.awt.Shape;
046import java.util.ArrayList;
047import java.util.Iterator;
048
049import javax.swing.SwingUtilities;
050import javax.swing.plaf.TextUI;
051
052/**
053 * The default highlight for Swing text components. It highlights text
054 * by filling the background with a rectangle.
055 */
056public class DefaultHighlighter extends LayeredHighlighter
057{
058  public static class DefaultHighlightPainter
059    extends LayerPainter
060  {
061    private Color color;
062
063    public DefaultHighlightPainter(Color c)
064    {
065      super();
066      color = c;
067    }
068
069    public Color getColor()
070    {
071      return color;
072    }
073
074    public void paint(Graphics g, int p0, int p1, Shape bounds,
075                      JTextComponent t)
076    {
077      if (p0 == p1)
078        return;
079
080      Rectangle rect = bounds.getBounds();
081
082      Color col = getColor();
083      if (col == null)
084        col = t.getSelectionColor();
085      g.setColor(col);
086
087      TextUI ui = t.getUI();
088
089      try
090        {
091
092          Rectangle l0 = ui.modelToView(t, p0, null);
093          Rectangle l1 = ui.modelToView(t, p1, null);
094
095          // Note: The computed locations may lie outside of the allocation
096          // area if the text is scrolled.
097
098          if (l0.y == l1.y)
099          {
100            SwingUtilities.computeUnion(l0.x, l0.y, l0.width, l0.height, l1);
101
102            // Paint only inside the allocation area.
103            SwingUtilities.computeIntersection(rect.x, rect.y, rect.width,
104                                               rect.height, l1);
105
106            g.fillRect(l1.x, l1.y, l1.width, l1.height);
107          }
108        else
109          {
110            // 1. The line of p0 is painted from the position of p0
111            // to the right border.
112            // 2. All lines between the ones where p0 and p1 lie on
113            // are completely highlighted. The allocation area is used to find
114            // out the bounds.
115            // 3. The final line is painted from the left border to the
116            // position of p1.
117
118            int firstLineWidth = rect.x + rect.width - l0.x;
119            g.fillRect(l0.x, l0.y, firstLineWidth, l0.height);
120            if (l0.y + l0.height != l1.y)
121              {
122                g.fillRect(rect.x, l0.y + l0.height, rect.width,
123                           l1.y - l0.y - l0.height);
124              }
125            g.fillRect(rect.x, l1.y, l1.x - rect.x, l1.height);
126          }
127      }
128    catch (BadLocationException ex)
129      {
130        // Can't render. Comment out for debugging.
131        // ex.printStackTrace();
132      }
133    }
134
135    public Shape paintLayer(Graphics g, int p0, int p1, Shape bounds,
136                            JTextComponent c, View view)
137    {
138      Color col = getColor();
139      if (col == null)
140        col = c.getSelectionColor();
141      g.setColor(col);
142
143      Rectangle rect = null;
144      if (p0 == view.getStartOffset() && p1 == view.getEndOffset())
145        {
146          // Paint complete bounds region.
147          rect = bounds instanceof Rectangle ? (Rectangle) bounds
148                                             : bounds.getBounds();
149        }
150      else
151        {
152          // Only partly inside the view.
153          try
154            {
155              Shape s = view.modelToView(p0, Position.Bias.Forward,
156                                         p1, Position.Bias.Backward,
157                                         bounds);
158              rect = s instanceof Rectangle ? (Rectangle) s : s.getBounds();
159            }
160          catch (BadLocationException ex)
161            {
162              // Can't render the highlight.
163            }
164        }
165
166      if (rect != null)
167        {
168          g.fillRect(rect.x, rect.y, rect.width, rect.height);
169        }
170      return rect;
171    }
172  }
173
174  private class HighlightEntry implements Highlighter.Highlight
175  {
176    Position p0;
177    Position p1;
178    Highlighter.HighlightPainter painter;
179
180    public HighlightEntry(Position p0, Position p1,
181                          Highlighter.HighlightPainter painter)
182    {
183      this.p0 = p0;
184      this.p1 = p1;
185      this.painter = painter;
186    }
187
188    public int getStartOffset()
189    {
190      return p0.getOffset();
191    }
192
193    public int getEndOffset()
194    {
195      return p1.getOffset();
196    }
197
198    public Highlighter.HighlightPainter getPainter()
199    {
200      return painter;
201    }
202  }
203
204  /**
205   * A HighlightEntry that is used for LayerPainter painters. In addition
206   * to the info maintained by the HighlightEntry, this class maintains
207   * a painting rectangle. This is used as repaint region when the
208   * highlight changes and the text component needs repainting.
209   */
210  private class LayerHighlightEntry
211    extends HighlightEntry
212  {
213
214    /**
215     * The paint rectangle.
216     */
217    Rectangle paintRect = new Rectangle();
218
219    LayerHighlightEntry(Position p0, Position p1,
220                        Highlighter.HighlightPainter p)
221    {
222      super(p0, p1, p);
223    }
224
225    /**
226     * Paints the highlight by calling the LayerPainter. This
227     * restricts the area to be painted by startOffset and endOffset
228     * and manages the paint rectangle.
229     */
230    void paintLayeredHighlight(Graphics g, int p0, int p1, Shape bounds,
231                               JTextComponent tc, View view)
232    {
233      p0 = Math.max(getStartOffset(), p0);
234      p1 = Math.min(getEndOffset(), p1);
235
236      Highlighter.HighlightPainter painter = getPainter();
237      if (painter instanceof LayerPainter)
238        {
239          LayerPainter layerPainter = (LayerPainter) painter;
240          Shape area = layerPainter.paintLayer(g, p0, p1, bounds, tc, view);
241          Rectangle rect;
242          if (area instanceof Rectangle && paintRect != null)
243            rect = (Rectangle) area;
244          else
245            rect = area.getBounds();
246
247          if (paintRect.width == 0 || paintRect.height == 0)
248            paintRect = rect.getBounds();
249          else
250            paintRect = SwingUtilities.computeUnion(rect.x, rect.y, rect.width,
251                                                    rect.height, paintRect);
252        }
253    }
254  }
255
256  /**
257   * @specnote final as of 1.4
258   */
259  public static final LayeredHighlighter.LayerPainter DefaultPainter =
260    new DefaultHighlightPainter(null);
261
262  private JTextComponent textComponent;
263  private ArrayList highlights = new ArrayList();
264  private boolean drawsLayeredHighlights = true;
265
266  public DefaultHighlighter()
267  {
268    // Nothing to do here.
269  }
270
271  public boolean getDrawsLayeredHighlights()
272  {
273    return drawsLayeredHighlights;
274  }
275
276  public void setDrawsLayeredHighlights(boolean newValue)
277  {
278    drawsLayeredHighlights = newValue;
279  }
280
281  private void checkPositions(int p0, int p1)
282    throws BadLocationException
283  {
284    if (p0 < 0)
285      throw new BadLocationException("DefaultHighlighter", p0);
286
287    if (p1 < p0)
288      throw new BadLocationException("DefaultHighlighter", p1);
289  }
290
291  public void install(JTextComponent c)
292  {
293    textComponent = c;
294    removeAllHighlights();
295  }
296
297  public void deinstall(JTextComponent c)
298  {
299    textComponent = null;
300  }
301
302  public Object addHighlight(int p0, int p1,
303                             Highlighter.HighlightPainter painter)
304    throws BadLocationException
305  {
306    checkPositions(p0, p1);
307    HighlightEntry entry;
308    Document doc = textComponent.getDocument();
309    Position pos0 = doc.createPosition(p0);
310    Position pos1 = doc.createPosition(p1);
311    if (getDrawsLayeredHighlights() && painter instanceof LayerPainter)
312      entry = new LayerHighlightEntry(pos0, pos1, painter);
313    else
314      entry = new HighlightEntry(pos0, pos1, painter);
315    highlights.add(entry);
316
317    textComponent.getUI().damageRange(textComponent, p0, p1);
318
319    return entry;
320  }
321
322  public void removeHighlight(Object tag)
323  {
324    HighlightEntry entry = (HighlightEntry) tag;
325    if (entry instanceof LayerHighlightEntry)
326      {
327        LayerHighlightEntry lEntry = (LayerHighlightEntry) entry;
328        Rectangle paintRect = lEntry.paintRect;
329        textComponent.repaint(paintRect.x, paintRect.y, paintRect.width,
330                              paintRect.height);
331      }
332    else
333      {
334        textComponent.getUI().damageRange(textComponent,
335                                          entry.getStartOffset(),
336                                          entry.getEndOffset());
337      }
338    highlights.remove(tag);
339
340  }
341
342  public void removeAllHighlights()
343  {
344    // Repaint damaged region.
345    int minX = 0;
346    int maxX = 0;
347    int minY = 0;
348    int maxY = 0;
349    int p0 = -1;
350    int p1 = -1;
351    for (Iterator i = highlights.iterator(); i.hasNext();)
352      {
353        HighlightEntry e = (HighlightEntry) i.next();
354        if (e instanceof LayerHighlightEntry)
355          {
356            LayerHighlightEntry le = (LayerHighlightEntry) e;
357            Rectangle r = le.paintRect;
358            minX = Math.min(r.x, minX);
359            maxX = Math.max(r.x + r.width, maxX);
360            minY = Math.min(r.y, minY);
361            maxY = Math.max(r.y + r.height, maxY);
362          }
363        else
364          {
365            if (p0 == -1 || p1 == -1)
366              {
367                p0 = e.getStartOffset();
368                p1 = e.getEndOffset();
369              }
370            else
371              {
372                p0 = Math.min(p0, e.getStartOffset());
373                p1 = Math.max(p1, e.getEndOffset());
374              }
375          }
376        if (minX != maxX && minY != maxY)
377          textComponent.repaint(minX, minY, maxX - minX, maxY - minY);
378        if (p0 != -1 && p1 != -1)
379          {
380            TextUI ui = textComponent.getUI();
381            ui.damageRange(textComponent, p0, p1);
382          }
383
384      }
385    highlights.clear();
386  }
387
388  public Highlighter.Highlight[] getHighlights()
389  {
390    return (Highlighter.Highlight[])
391      highlights.toArray(new Highlighter.Highlight[highlights.size()]);
392  }
393
394  public void changeHighlight(Object tag, int n0, int n1)
395    throws BadLocationException
396  {
397    Document doc = textComponent.getDocument();
398    TextUI ui = textComponent.getUI();
399    if (tag instanceof LayerHighlightEntry)
400      {
401        LayerHighlightEntry le = (LayerHighlightEntry) tag;
402        Rectangle r = le.paintRect;
403        if (r.width > 0 && r.height > 0)
404          textComponent.repaint(r.x, r.y, r.width, r.height);
405        r.width = 0;
406        r.height = 0;
407        le.p0 = doc.createPosition(n0);
408        le.p1 = doc.createPosition(n1);
409        ui.damageRange(textComponent, Math.min(n0, n1), Math.max(n0, n1));
410      }
411    else if (tag instanceof HighlightEntry)
412      {
413        HighlightEntry e = (HighlightEntry) tag;
414        int p0 = e.getStartOffset();
415        int p1 = e.getEndOffset();
416        if (p0 == n0)
417          {
418            ui.damageRange(textComponent, Math.min(p1, n1),
419                           Math.max(p1, n1));
420          }
421        else if (n1 == p1)
422          {
423            ui.damageRange(textComponent, Math.min(p0, n0),
424                           Math.max(p0, n0));
425          }
426        else
427          {
428            ui.damageRange(textComponent, p0, p1);
429            ui.damageRange(textComponent, n0, n1);
430          }
431        e.p0 = doc.createPosition(n0);
432        e.p1 = doc.createPosition(n1);
433      }
434  }
435
436  public void paintLayeredHighlights(Graphics g, int p0, int p1,
437                                     Shape viewBounds, JTextComponent editor,
438                                     View view)
439  {
440    for (Iterator i = highlights.iterator(); i.hasNext();)
441      {
442        Object o = i.next();
443        if (o instanceof LayerHighlightEntry)
444          {
445            LayerHighlightEntry entry = (LayerHighlightEntry) o;
446            int start = entry.getStartOffset();
447            int end = entry.getEndOffset();
448            if ((p0 < start && p1 > start) || (p0 >= start && p0 < end))
449              entry.paintLayeredHighlight(g, p0, p1, viewBounds, editor, view);
450          }
451      }
452  }
453
454  public void paint(Graphics g)
455  {
456    int size = highlights.size();
457
458    // Check if there are any highlights.
459    if (size == 0)
460      return;
461
462    // Prepares the rectangle of the inner drawing area.
463    Insets insets = textComponent.getInsets();
464    Shape bounds =
465      new Rectangle(insets.left,
466                    insets.top,
467                    textComponent.getWidth() - insets.left - insets.right,
468                    textComponent.getHeight() - insets.top - insets.bottom);
469
470    for (int index = 0; index < size; ++index)
471      {
472        HighlightEntry entry = (HighlightEntry) highlights.get(index);
473        if (! (entry instanceof LayerHighlightEntry))
474          entry.painter.paint(g, entry.getStartOffset(), entry.getEndOffset(),
475                              bounds, textComponent);
476      }
477  }
478}