001    /* DefaultHighlighter.java -- The default highlight for Swing
002       Copyright (C) 2004, 2006  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing.text;
040    
041    import java.awt.Color;
042    import java.awt.Graphics;
043    import java.awt.Insets;
044    import java.awt.Rectangle;
045    import java.awt.Shape;
046    import java.util.ArrayList;
047    import java.util.Iterator;
048    
049    import javax.swing.SwingUtilities;
050    import 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     */
056    public 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    }