Home » openjdk-7 » javax » swing » text » [javadoc | source]

    1   /*
    2    * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved.
    3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    4    *
    5    * This code is free software; you can redistribute it and/or modify it
    6    * under the terms of the GNU General Public License version 2 only, as
    7    * published by the Free Software Foundation.  Oracle designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Oracle in the LICENSE file that accompanied this code.
   10    *
   11    * This code is distributed in the hope that it will be useful, but WITHOUT
   12    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   13    * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14    * version 2 for more details (a copy is included in the LICENSE file that
   15    * accompanied this code).
   16    *
   17    * You should have received a copy of the GNU General Public License version
   18    * 2 along with this work; if not, write to the Free Software Foundation,
   19    * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   20    *
   21    * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   22    * or visit www.oracle.com if you need additional information or have any
   23    * questions.
   24    */
   25   package javax.swing.text;
   26   
   27   import java.util.Vector;
   28   import java.awt;
   29   import javax.swing.plaf;
   30   import javax.swing;
   31   
   32   /**
   33    * Implements the Highlighter interfaces.  Implements a simple highlight
   34    * painter that renders in a solid color.
   35    *
   36    * @author  Timothy Prinzing
   37    * @see     Highlighter
   38    */
   39   public class DefaultHighlighter extends LayeredHighlighter {
   40   
   41       /**
   42        * Creates a new DefaultHighlighther object.
   43        */
   44       public DefaultHighlighter() {
   45           drawsLayeredHighlights = true;
   46       }
   47   
   48       // ---- Highlighter methods ----------------------------------------------
   49   
   50       /**
   51        * Renders the highlights.
   52        *
   53        * @param g the graphics context
   54        */
   55       public void paint(Graphics g) {
   56           // PENDING(prinz) - should cull ranges not visible
   57           int len = highlights.size();
   58           for (int i = 0; i < len; i++) {
   59               HighlightInfo info = highlights.elementAt(i);
   60               if (!(info instanceof LayeredHighlightInfo)) {
   61                   // Avoid allocing unless we need it.
   62                   Rectangle a = component.getBounds();
   63                   Insets insets = component.getInsets();
   64                   a.x = insets.left;
   65                   a.y = insets.top;
   66                   a.width -= insets.left + insets.right;
   67                   a.height -= insets.top + insets.bottom;
   68                   for (; i < len; i++) {
   69                       info = highlights.elementAt(i);
   70                       if (!(info instanceof LayeredHighlightInfo)) {
   71                           Highlighter.HighlightPainter p = info.getPainter();
   72                           p.paint(g, info.getStartOffset(), info.getEndOffset(),
   73                                   a, component);
   74                       }
   75                   }
   76               }
   77           }
   78       }
   79   
   80       /**
   81        * Called when the UI is being installed into the
   82        * interface of a JTextComponent.  Installs the editor, and
   83        * removes any existing highlights.
   84        *
   85        * @param c the editor component
   86        * @see Highlighter#install
   87        */
   88       public void install(JTextComponent c) {
   89           component = c;
   90           removeAllHighlights();
   91       }
   92   
   93       /**
   94        * Called when the UI is being removed from the interface of
   95        * a JTextComponent.
   96        *
   97        * @param c the component
   98        * @see Highlighter#deinstall
   99        */
  100       public void deinstall(JTextComponent c) {
  101           component = null;
  102       }
  103   
  104       /**
  105        * Adds a highlight to the view.  Returns a tag that can be used
  106        * to refer to the highlight.
  107        *
  108        * @param p0   the start offset of the range to highlight >= 0
  109        * @param p1   the end offset of the range to highlight >= p0
  110        * @param p    the painter to use to actually render the highlight
  111        * @return     an object that can be used as a tag
  112        *   to refer to the highlight
  113        * @exception BadLocationException if the specified location is invalid
  114        */
  115       public Object addHighlight(int p0, int p1, Highlighter.HighlightPainter p) throws BadLocationException {
  116           if (p0 < 0) {
  117               throw new BadLocationException("Invalid start offset", p0);
  118           }
  119   
  120           if (p1 < p0) {
  121               throw new BadLocationException("Invalid end offset", p1);
  122           }
  123   
  124           Document doc = component.getDocument();
  125           HighlightInfo i = (getDrawsLayeredHighlights() &&
  126                              (p instanceof LayeredHighlighter.LayerPainter)) ?
  127                             new LayeredHighlightInfo() : new HighlightInfo();
  128           i.painter = p;
  129           i.p0 = doc.createPosition(p0);
  130           i.p1 = doc.createPosition(p1);
  131           highlights.addElement(i);
  132           safeDamageRange(p0, p1);
  133           return i;
  134       }
  135   
  136       /**
  137        * Removes a highlight from the view.
  138        *
  139        * @param tag the reference to the highlight
  140        */
  141       public void removeHighlight(Object tag) {
  142           if (tag instanceof LayeredHighlightInfo) {
  143               LayeredHighlightInfo lhi = (LayeredHighlightInfo)tag;
  144               if (lhi.width > 0 && lhi.height > 0) {
  145                   component.repaint(lhi.x, lhi.y, lhi.width, lhi.height);
  146               }
  147           }
  148           else {
  149               HighlightInfo info = (HighlightInfo) tag;
  150               safeDamageRange(info.p0, info.p1);
  151           }
  152           highlights.removeElement(tag);
  153       }
  154   
  155       /**
  156        * Removes all highlights.
  157        */
  158       public void removeAllHighlights() {
  159           TextUI mapper = component.getUI();
  160           if (getDrawsLayeredHighlights()) {
  161               int len = highlights.size();
  162               if (len != 0) {
  163                   int minX = 0;
  164                   int minY = 0;
  165                   int maxX = 0;
  166                   int maxY = 0;
  167                   int p0 = -1;
  168                   int p1 = -1;
  169                   for (int i = 0; i < len; i++) {
  170                       HighlightInfo hi = highlights.elementAt(i);
  171                       if (hi instanceof LayeredHighlightInfo) {
  172                           LayeredHighlightInfo info = (LayeredHighlightInfo)hi;
  173                           minX = Math.min(minX, info.x);
  174                           minY = Math.min(minY, info.y);
  175                           maxX = Math.max(maxX, info.x + info.width);
  176                           maxY = Math.max(maxY, info.y + info.height);
  177                       }
  178                       else {
  179                           if (p0 == -1) {
  180                               p0 = hi.p0.getOffset();
  181                               p1 = hi.p1.getOffset();
  182                           }
  183                           else {
  184                               p0 = Math.min(p0, hi.p0.getOffset());
  185                               p1 = Math.max(p1, hi.p1.getOffset());
  186                           }
  187                       }
  188                   }
  189                   if (minX != maxX && minY != maxY) {
  190                       component.repaint(minX, minY, maxX - minX, maxY - minY);
  191                   }
  192                   if (p0 != -1) {
  193                       try {
  194                           safeDamageRange(p0, p1);
  195                       } catch (BadLocationException e) {}
  196                   }
  197                   highlights.removeAllElements();
  198               }
  199           }
  200           else if (mapper != null) {
  201               int len = highlights.size();
  202               if (len != 0) {
  203                   int p0 = Integer.MAX_VALUE;
  204                   int p1 = 0;
  205                   for (int i = 0; i < len; i++) {
  206                       HighlightInfo info = highlights.elementAt(i);
  207                       p0 = Math.min(p0, info.p0.getOffset());
  208                       p1 = Math.max(p1, info.p1.getOffset());
  209                   }
  210                   try {
  211                       safeDamageRange(p0, p1);
  212                   } catch (BadLocationException e) {}
  213   
  214                   highlights.removeAllElements();
  215               }
  216           }
  217       }
  218   
  219       /**
  220        * Changes a highlight.
  221        *
  222        * @param tag the highlight tag
  223        * @param p0 the beginning of the range >= 0
  224        * @param p1 the end of the range >= p0
  225        * @exception BadLocationException if the specified location is invalid
  226        */
  227       public void changeHighlight(Object tag, int p0, int p1) throws BadLocationException {
  228           if (p0 < 0) {
  229               throw new BadLocationException("Invalid beginning of the range", p0);
  230           }
  231   
  232           if (p1 < p0) {
  233               throw new BadLocationException("Invalid end of the range", p1);
  234           }
  235   
  236           Document doc = component.getDocument();
  237           if (tag instanceof LayeredHighlightInfo) {
  238               LayeredHighlightInfo lhi = (LayeredHighlightInfo)tag;
  239               if (lhi.width > 0 && lhi.height > 0) {
  240                   component.repaint(lhi.x, lhi.y, lhi.width, lhi.height);
  241               }
  242               // Mark the highlights region as invalid, it will reset itself
  243               // next time asked to paint.
  244               lhi.width = lhi.height = 0;
  245               lhi.p0 = doc.createPosition(p0);
  246               lhi.p1 = doc.createPosition(p1);
  247               safeDamageRange(Math.min(p0, p1), Math.max(p0, p1));
  248           }
  249           else {
  250               HighlightInfo info = (HighlightInfo) tag;
  251               int oldP0 = info.p0.getOffset();
  252               int oldP1 = info.p1.getOffset();
  253               if (p0 == oldP0) {
  254                   safeDamageRange(Math.min(oldP1, p1),
  255                                      Math.max(oldP1, p1));
  256               } else if (p1 == oldP1) {
  257                   safeDamageRange(Math.min(p0, oldP0),
  258                                      Math.max(p0, oldP0));
  259               } else {
  260                   safeDamageRange(oldP0, oldP1);
  261                   safeDamageRange(p0, p1);
  262               }
  263               info.p0 = doc.createPosition(p0);
  264               info.p1 = doc.createPosition(p1);
  265           }
  266       }
  267   
  268       /**
  269        * Makes a copy of the highlights.  Does not actually clone each highlight,
  270        * but only makes references to them.
  271        *
  272        * @return the copy
  273        * @see Highlighter#getHighlights
  274        */
  275       public Highlighter.Highlight[] getHighlights() {
  276           int size = highlights.size();
  277           if (size == 0) {
  278               return noHighlights;
  279           }
  280           Highlighter.Highlight[] h = new Highlighter.Highlight[size];
  281           highlights.copyInto(h);
  282           return h;
  283       }
  284   
  285       /**
  286        * When leaf Views (such as LabelView) are rendering they should
  287        * call into this method. If a highlight is in the given region it will
  288        * be drawn immediately.
  289        *
  290        * @param g Graphics used to draw
  291        * @param p0 starting offset of view
  292        * @param p1 ending offset of view
  293        * @param viewBounds Bounds of View
  294        * @param editor JTextComponent
  295        * @param view View instance being rendered
  296        */
  297       public void paintLayeredHighlights(Graphics g, int p0, int p1,
  298                                          Shape viewBounds,
  299                                          JTextComponent editor, View view) {
  300           for (int counter = highlights.size() - 1; counter >= 0; counter--) {
  301               HighlightInfo tag = highlights.elementAt(counter);
  302               if (tag instanceof LayeredHighlightInfo) {
  303                   LayeredHighlightInfo lhi = (LayeredHighlightInfo)tag;
  304                   int start = lhi.getStartOffset();
  305                   int end = lhi.getEndOffset();
  306                   if ((p0 < start && p1 > start) ||
  307                       (p0 >= start && p0 < end)) {
  308                       lhi.paintLayeredHighlights(g, p0, p1, viewBounds,
  309                                                  editor, view);
  310                   }
  311               }
  312           }
  313       }
  314   
  315       /**
  316        * Queues damageRange() call into event dispatch thread
  317        * to be sure that views are in consistent state.
  318        */
  319       private void safeDamageRange(final Position p0, final Position p1) {
  320           safeDamager.damageRange(p0, p1);
  321       }
  322   
  323       /**
  324        * Queues damageRange() call into event dispatch thread
  325        * to be sure that views are in consistent state.
  326        */
  327       private void safeDamageRange(int a0, int a1) throws BadLocationException {
  328           Document doc = component.getDocument();
  329           safeDamageRange(doc.createPosition(a0), doc.createPosition(a1));
  330       }
  331   
  332       /**
  333        * If true, highlights are drawn as the Views draw the text. That is
  334        * the Views will call into <code>paintLayeredHighlight</code> which
  335        * will result in a rectangle being drawn before the text is drawn
  336        * (if the offsets are in a highlighted region that is). For this to
  337        * work the painter supplied must be an instance of
  338        * LayeredHighlightPainter.
  339        */
  340       public void setDrawsLayeredHighlights(boolean newValue) {
  341           drawsLayeredHighlights = newValue;
  342       }
  343   
  344       public boolean getDrawsLayeredHighlights() {
  345           return drawsLayeredHighlights;
  346       }
  347   
  348       // ---- member variables --------------------------------------------
  349   
  350       private final static Highlighter.Highlight[] noHighlights =
  351               new Highlighter.Highlight[0];
  352       private Vector<HighlightInfo> highlights = new Vector<HighlightInfo>();
  353       private JTextComponent component;
  354       private boolean drawsLayeredHighlights;
  355       private SafeDamager safeDamager = new SafeDamager();
  356   
  357   
  358       /**
  359        * Default implementation of LayeredHighlighter.LayerPainter that can
  360        * be used for painting highlights.
  361        * <p>
  362        * As of 1.4 this field is final.
  363        */
  364       public static final LayeredHighlighter.LayerPainter DefaultPainter = new DefaultHighlightPainter(null);
  365   
  366   
  367       /**
  368        * Simple highlight painter that fills a highlighted area with
  369        * a solid color.
  370        */
  371       public static class DefaultHighlightPainter extends LayeredHighlighter.LayerPainter {
  372   
  373           /**
  374            * Constructs a new highlight painter. If <code>c</code> is null,
  375            * the JTextComponent will be queried for its selection color.
  376            *
  377            * @param c the color for the highlight
  378            */
  379           public DefaultHighlightPainter(Color c) {
  380               color = c;
  381           }
  382   
  383           /**
  384            * Returns the color of the highlight.
  385            *
  386            * @return the color
  387            */
  388           public Color getColor() {
  389               return color;
  390           }
  391   
  392           // --- HighlightPainter methods ---------------------------------------
  393   
  394           /**
  395            * Paints a highlight.
  396            *
  397            * @param g the graphics context
  398            * @param offs0 the starting model offset >= 0
  399            * @param offs1 the ending model offset >= offs1
  400            * @param bounds the bounding box for the highlight
  401            * @param c the editor
  402            */
  403           public void paint(Graphics g, int offs0, int offs1, Shape bounds, JTextComponent c) {
  404               Rectangle alloc = bounds.getBounds();
  405               try {
  406                   // --- determine locations ---
  407                   TextUI mapper = c.getUI();
  408                   Rectangle p0 = mapper.modelToView(c, offs0);
  409                   Rectangle p1 = mapper.modelToView(c, offs1);
  410   
  411                   // --- render ---
  412                   Color color = getColor();
  413   
  414                   if (color == null) {
  415                       g.setColor(c.getSelectionColor());
  416                   }
  417                   else {
  418                       g.setColor(color);
  419                   }
  420                   if (p0.y == p1.y) {
  421                       // same line, render a rectangle
  422                       Rectangle r = p0.union(p1);
  423                       g.fillRect(r.x, r.y, r.width, r.height);
  424                   } else {
  425                       // different lines
  426                       int p0ToMarginWidth = alloc.x + alloc.width - p0.x;
  427                       g.fillRect(p0.x, p0.y, p0ToMarginWidth, p0.height);
  428                       if ((p0.y + p0.height) != p1.y) {
  429                           g.fillRect(alloc.x, p0.y + p0.height, alloc.width,
  430                                      p1.y - (p0.y + p0.height));
  431                       }
  432                       g.fillRect(alloc.x, p1.y, (p1.x - alloc.x), p1.height);
  433                   }
  434               } catch (BadLocationException e) {
  435                   // can't render
  436               }
  437           }
  438   
  439           // --- LayerPainter methods ----------------------------
  440           /**
  441            * Paints a portion of a highlight.
  442            *
  443            * @param g the graphics context
  444            * @param offs0 the starting model offset >= 0
  445            * @param offs1 the ending model offset >= offs1
  446            * @param bounds the bounding box of the view, which is not
  447            *        necessarily the region to paint.
  448            * @param c the editor
  449            * @param view View painting for
  450            * @return region drawing occured in
  451            */
  452           public Shape paintLayer(Graphics g, int offs0, int offs1,
  453                                   Shape bounds, JTextComponent c, View view) {
  454               Color color = getColor();
  455   
  456               if (color == null) {
  457                   g.setColor(c.getSelectionColor());
  458               }
  459               else {
  460                   g.setColor(color);
  461               }
  462   
  463               Rectangle r;
  464   
  465               if (offs0 == view.getStartOffset() &&
  466                   offs1 == view.getEndOffset()) {
  467                   // Contained in view, can just use bounds.
  468                   if (bounds instanceof Rectangle) {
  469                       r = (Rectangle) bounds;
  470                   }
  471                   else {
  472                       r = bounds.getBounds();
  473                   }
  474               }
  475               else {
  476                   // Should only render part of View.
  477                   try {
  478                       // --- determine locations ---
  479                       Shape shape = view.modelToView(offs0, Position.Bias.Forward,
  480                                                      offs1,Position.Bias.Backward,
  481                                                      bounds);
  482                       r = (shape instanceof Rectangle) ?
  483                                     (Rectangle)shape : shape.getBounds();
  484                   } catch (BadLocationException e) {
  485                       // can't render
  486                       r = null;
  487                   }
  488               }
  489   
  490               if (r != null) {
  491                   // If we are asked to highlight, we should draw something even
  492                   // if the model-to-view projection is of zero width (6340106).
  493                   r.width = Math.max(r.width, 1);
  494   
  495                   g.fillRect(r.x, r.y, r.width, r.height);
  496               }
  497   
  498               return r;
  499           }
  500   
  501           private Color color;
  502   
  503       }
  504   
  505   
  506       class HighlightInfo implements Highlighter.Highlight {
  507   
  508           public int getStartOffset() {
  509               return p0.getOffset();
  510           }
  511   
  512           public int getEndOffset() {
  513               return p1.getOffset();
  514           }
  515   
  516           public Highlighter.HighlightPainter getPainter() {
  517               return painter;
  518           }
  519   
  520           Position p0;
  521           Position p1;
  522           Highlighter.HighlightPainter painter;
  523       }
  524   
  525   
  526       /**
  527        * LayeredHighlightPainter is used when a drawsLayeredHighlights is
  528        * true. It maintains a rectangle of the region to paint.
  529        */
  530       class LayeredHighlightInfo extends HighlightInfo {
  531   
  532           void union(Shape bounds) {
  533               if (bounds == null)
  534                   return;
  535   
  536               Rectangle alloc;
  537               if (bounds instanceof Rectangle) {
  538                   alloc = (Rectangle)bounds;
  539               }
  540               else {
  541                   alloc = bounds.getBounds();
  542               }
  543               if (width == 0 || height == 0) {
  544                   x = alloc.x;
  545                   y = alloc.y;
  546                   width = alloc.width;
  547                   height = alloc.height;
  548               }
  549               else {
  550                   width = Math.max(x + width, alloc.x + alloc.width);
  551                   height = Math.max(y + height, alloc.y + alloc.height);
  552                   x = Math.min(x, alloc.x);
  553                   width -= x;
  554                   y = Math.min(y, alloc.y);
  555                   height -= y;
  556               }
  557           }
  558   
  559           /**
  560            * Restricts the region based on the receivers offsets and messages
  561            * the painter to paint the region.
  562            */
  563           void paintLayeredHighlights(Graphics g, int p0, int p1,
  564                                       Shape viewBounds, JTextComponent editor,
  565                                       View view) {
  566               int start = getStartOffset();
  567               int end = getEndOffset();
  568               // Restrict the region to what we represent
  569               p0 = Math.max(start, p0);
  570               p1 = Math.min(end, p1);
  571               // Paint the appropriate region using the painter and union
  572               // the effected region with our bounds.
  573               union(((LayeredHighlighter.LayerPainter)painter).paintLayer
  574                     (g, p0, p1, viewBounds, editor, view));
  575           }
  576   
  577           int x;
  578           int y;
  579           int width;
  580           int height;
  581       }
  582   
  583       /**
  584        * This class invokes <code>mapper.damageRange</code> in
  585        * EventDispatchThread. The only one instance per Highlighter
  586        * is cretaed. When a number of ranges should be damaged
  587        * it collects them into queue and damages
  588        * them in consecutive order in <code>run</code>
  589        * call.
  590        */
  591       class SafeDamager implements Runnable {
  592           private Vector<Position> p0 = new Vector<Position>(10);
  593           private Vector<Position> p1 = new Vector<Position>(10);
  594           private Document lastDoc = null;
  595   
  596           /**
  597            * Executes range(s) damage and cleans range queue.
  598            */
  599           public synchronized void run() {
  600               if (component != null) {
  601                   TextUI mapper = component.getUI();
  602                   if (mapper != null && lastDoc == component.getDocument()) {
  603                       // the Document should be the same to properly
  604                       // display highlights
  605                       int len = p0.size();
  606                       for (int i = 0; i < len; i++){
  607                           mapper.damageRange(component,
  608                                   p0.get(i).getOffset(),
  609                                   p1.get(i).getOffset());
  610                       }
  611                   }
  612               }
  613               p0.clear();
  614               p1.clear();
  615   
  616               // release reference
  617               lastDoc = null;
  618           }
  619   
  620           /**
  621            * Adds the range to be damaged into the range queue. If the
  622            * range queue is empty (the first call or run() was already
  623            * invoked) then adds this class instance into EventDispatch
  624            * queue.
  625            *
  626            * The method also tracks if the current document changed or
  627            * component is null. In this case it removes all ranges added
  628            * before from range queue.
  629            */
  630           public synchronized void damageRange(Position pos0, Position pos1) {
  631               if (component == null) {
  632                   p0.clear();
  633                   lastDoc = null;
  634                   return;
  635               }
  636   
  637               boolean addToQueue = p0.isEmpty();
  638               Document curDoc = component.getDocument();
  639               if (curDoc != lastDoc) {
  640                   if (!p0.isEmpty()) {
  641                       p0.clear();
  642                       p1.clear();
  643                   }
  644                   lastDoc = curDoc;
  645               }
  646               p0.add(pos0);
  647               p1.add(pos1);
  648   
  649               if (addToQueue) {
  650                   SwingUtilities.invokeLater(this);
  651               }
  652           }
  653       }
  654   }

Home » openjdk-7 » javax » swing » text » [javadoc | source]