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

    1   /*
    2    * Copyright (c) 1998, 2008, 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.event;
   30   
   31   /**
   32    * ZoneView is a View implementation that creates zones for which
   33    * the child views are not created or stored until they are needed
   34    * for display or model/view translations.  This enables a substantial
   35    * reduction in memory consumption for situations where the model
   36    * being represented is very large, by building view objects only for
   37    * the region being actively viewed/edited.  The size of the children
   38    * can be estimated in some way, or calculated asynchronously with
   39    * only the result being saved.
   40    * <p>
   41    * ZoneView extends BoxView to provide a box that implements
   42    * zones for its children.  The zones are special View implementations
   43    * (the children of an instance of this class) that represent only a
   44    * portion of the model that an instance of ZoneView is responsible
   45    * for.  The zones don't create child views until an attempt is made
   46    * to display them. A box shaped view is well suited to this because:
   47    *   <ul>
   48    *   <li>
   49    *   Boxes are a heavily used view, and having a box that
   50    *   provides this behavior gives substantial opportunity
   51    *   to plug the behavior into a view hierarchy from the
   52    *   view factory.
   53    *   <li>
   54    *   Boxes are tiled in one direction, so it is easy to
   55    *   divide them into zones in a reliable way.
   56    *   <li>
   57    *   Boxes typically have a simple relationship to the model (i.e. they
   58    *   create child views that directly represent the child elements).
   59    *   <li>
   60    *   Boxes are easier to estimate the size of than some other shapes.
   61    *   </ul>
   62    * <p>
   63    * The default behavior is controled by two properties, maxZoneSize
   64    * and maxZonesLoaded.  Setting maxZoneSize to Integer.MAX_VALUE would
   65    * have the effect of causing only one zone to be created.  This would
   66    * effectively turn the view into an implementation of the decorator
   67    * pattern.  Setting maxZonesLoaded to a value of Integer.MAX_VALUE would
   68    * cause zones to never be unloaded.  For simplicity, zones are created on
   69    * boundaries represented by the child elements of the element the view is
   70    * responsible for.  The zones can be any View implementation, but the
   71    * default implementation is based upon AsyncBoxView which supports fairly
   72    * large zones efficiently.
   73    *
   74    * @author  Timothy Prinzing
   75    * @see     View
   76    * @since   1.3
   77    */
   78   public class ZoneView extends BoxView {
   79   
   80       int maxZoneSize = 8 * 1024;
   81       int maxZonesLoaded = 3;
   82       Vector<View> loadedZones;
   83   
   84       /**
   85        * Constructs a ZoneView.
   86        *
   87        * @param elem the element this view is responsible for
   88        * @param axis either View.X_AXIS or View.Y_AXIS
   89        */
   90       public ZoneView(Element elem, int axis) {
   91           super(elem, axis);
   92           loadedZones = new Vector<View>();
   93       }
   94   
   95       /**
   96        * Get the current maximum zone size.
   97        */
   98       public int getMaximumZoneSize() {
   99           return maxZoneSize;
  100       }
  101   
  102       /**
  103        * Set the desired maximum zone size.  A
  104        * zone may get larger than this size if
  105        * a single child view is larger than this
  106        * size since zones are formed on child view
  107        * boundaries.
  108        *
  109        * @param size the number of characters the zone
  110        * may represent before attempting to break
  111        * the zone into a smaller size.
  112        */
  113       public void setMaximumZoneSize(int size) {
  114           maxZoneSize = size;
  115       }
  116   
  117       /**
  118        * Get the current setting of the number of zones
  119        * allowed to be loaded at the same time.
  120        */
  121       public int getMaxZonesLoaded() {
  122           return maxZonesLoaded;
  123       }
  124   
  125       /**
  126        * Sets the current setting of the number of zones
  127        * allowed to be loaded at the same time. This will throw an
  128        * <code>IllegalArgumentException</code> if <code>mzl</code> is less
  129        * than 1.
  130        *
  131        * @param mzl the desired maximum number of zones
  132        *  to be actively loaded, must be greater than 0
  133        * @exception IllegalArgumentException if <code>mzl</code> is < 1
  134        */
  135       public void setMaxZonesLoaded(int mzl) {
  136           if (mzl < 1) {
  137               throw new IllegalArgumentException("ZoneView.setMaxZonesLoaded must be greater than 0.");
  138           }
  139           maxZonesLoaded = mzl;
  140           unloadOldZones();
  141       }
  142   
  143       /**
  144        * Called by a zone when it gets loaded.  This happens when
  145        * an attempt is made to display or perform a model/view
  146        * translation on a zone that was in an unloaded state.
  147        * This is imlemented to check if the maximum number of
  148        * zones was reached and to unload the oldest zone if so.
  149        *
  150        * @param zone the child view that was just loaded.
  151        */
  152       protected void zoneWasLoaded(View zone) {
  153           //System.out.println("loading: " + zone.getStartOffset() + "," + zone.getEndOffset());
  154           loadedZones.addElement(zone);
  155           unloadOldZones();
  156       }
  157   
  158       void unloadOldZones() {
  159           while (loadedZones.size() > getMaxZonesLoaded()) {
  160               View zone = loadedZones.elementAt(0);
  161               loadedZones.removeElementAt(0);
  162               unloadZone(zone);
  163           }
  164       }
  165   
  166       /**
  167        * Unload a zone (Convert the zone to its memory saving state).
  168        * The zones are expected to represent a subset of the
  169        * child elements of the element this view is responsible for.
  170        * Therefore, the default implementation is to simple remove
  171        * all the children.
  172        *
  173        * @param zone the child view desired to be set to an
  174        *  unloaded state.
  175        */
  176       protected void unloadZone(View zone) {
  177           //System.out.println("unloading: " + zone.getStartOffset() + "," + zone.getEndOffset());
  178           zone.removeAll();
  179       }
  180   
  181       /**
  182        * Determine if a zone is in the loaded state.
  183        * The zones are expected to represent a subset of the
  184        * child elements of the element this view is responsible for.
  185        * Therefore, the default implementation is to return
  186        * true if the view has children.
  187        */
  188       protected boolean isZoneLoaded(View zone) {
  189           return (zone.getViewCount() > 0);
  190       }
  191   
  192       /**
  193        * Create a view to represent a zone for the given
  194        * range within the model (which should be within
  195        * the range of this objects responsibility).  This
  196        * is called by the zone management logic to create
  197        * new zones.  Subclasses can provide a different
  198        * implementation for a zone by changing this method.
  199        *
  200        * @param p0 the start of the desired zone.  This should
  201        *  be >= getStartOffset() and < getEndOffset().  This
  202        *  value should also be < p1.
  203        * @param p1 the end of the desired zone.  This should
  204        *  be > getStartOffset() and <= getEndOffset().  This
  205        *  value should also be > p0.
  206        */
  207       protected View createZone(int p0, int p1) {
  208           Document doc = getDocument();
  209           View zone;
  210           try {
  211               zone = new Zone(getElement(),
  212                               doc.createPosition(p0),
  213                               doc.createPosition(p1));
  214           } catch (BadLocationException ble) {
  215               // this should puke in some way.
  216               throw new StateInvariantError(ble.getMessage());
  217           }
  218           return zone;
  219       }
  220   
  221       /**
  222        * Loads all of the children to initialize the view.
  223        * This is called by the <code>setParent</code> method.
  224        * This is reimplemented to not load any children directly
  225        * (as they are created by the zones).  This method creates
  226        * the initial set of zones.  Zones don't actually get
  227        * populated however until an attempt is made to display
  228        * them or to do model/view coordinate translation.
  229        *
  230        * @param f the view factory
  231        */
  232       protected void loadChildren(ViewFactory f) {
  233           // build the first zone.
  234           Document doc = getDocument();
  235           int offs0 = getStartOffset();
  236           int offs1 = getEndOffset();
  237           append(createZone(offs0, offs1));
  238           handleInsert(offs0, offs1 - offs0);
  239       }
  240   
  241       /**
  242        * Returns the child view index representing the given position in
  243        * the model.
  244        *
  245        * @param pos the position >= 0
  246        * @return  index of the view representing the given position, or
  247        *   -1 if no view represents that position
  248        */
  249       protected int getViewIndexAtPosition(int pos) {
  250           // PENDING(prinz) this could be done as a binary
  251           // search, and probably should be.
  252           int n = getViewCount();
  253           if (pos == getEndOffset()) {
  254               return n - 1;
  255           }
  256           for(int i = 0; i < n; i++) {
  257               View v = getView(i);
  258               if(pos >= v.getStartOffset() &&
  259                  pos < v.getEndOffset()) {
  260                   return i;
  261               }
  262           }
  263           return -1;
  264       }
  265   
  266       void handleInsert(int pos, int length) {
  267           int index = getViewIndex(pos, Position.Bias.Forward);
  268           View v = getView(index);
  269           int offs0 = v.getStartOffset();
  270           int offs1 = v.getEndOffset();
  271           if ((offs1 - offs0) > maxZoneSize) {
  272               splitZone(index, offs0, offs1);
  273           }
  274       }
  275   
  276       void handleRemove(int pos, int length) {
  277           // IMPLEMENT
  278       }
  279   
  280       /**
  281        * Break up the zone at the given index into pieces
  282        * of an acceptable size.
  283        */
  284       void splitZone(int index, int offs0, int offs1) {
  285           // divide the old zone into a new set of bins
  286           Element elem = getElement();
  287           Document doc = elem.getDocument();
  288           Vector<View> zones = new Vector<View>();
  289           int offs = offs0;
  290           do {
  291               offs0 = offs;
  292               offs = Math.min(getDesiredZoneEnd(offs0), offs1);
  293               zones.addElement(createZone(offs0, offs));
  294           } while (offs < offs1);
  295           View oldZone = getView(index);
  296           View[] newZones = new View[zones.size()];
  297           zones.copyInto(newZones);
  298           replace(index, 1, newZones);
  299       }
  300   
  301       /**
  302        * Returns the zone position to use for the
  303        * end of a zone that starts at the given
  304        * position.  By default this returns something
  305        * close to half the max zone size.
  306        */
  307       int getDesiredZoneEnd(int pos) {
  308           Element elem = getElement();
  309           int index = elem.getElementIndex(pos + (maxZoneSize / 2));
  310           Element child = elem.getElement(index);
  311           int offs0 = child.getStartOffset();
  312           int offs1 = child.getEndOffset();
  313           if ((offs1 - pos) > maxZoneSize) {
  314               if (offs0 > pos) {
  315                   return offs0;
  316               }
  317           }
  318           return offs1;
  319       }
  320   
  321       // ---- View methods ----------------------------------------------------
  322   
  323       /**
  324        * The superclass behavior will try to update the child views
  325        * which is not desired in this case, since the children are
  326        * zones and not directly effected by the changes to the
  327        * associated element.  This is reimplemented to do nothing
  328        * and return false.
  329        */
  330       protected boolean updateChildren(DocumentEvent.ElementChange ec,
  331                                        DocumentEvent e, ViewFactory f) {
  332           return false;
  333       }
  334   
  335       /**
  336        * Gives notification that something was inserted into the document
  337        * in a location that this view is responsible for.  This is largely
  338        * delegated to the superclass, but is reimplemented to update the
  339        * relevant zone (i.e. determine if a zone needs to be split into a
  340        * set of 2 or more zones).
  341        *
  342        * @param changes the change information from the associated document
  343        * @param a the current allocation of the view
  344        * @param f the factory to use to rebuild if the view has children
  345        * @see View#insertUpdate
  346        */
  347       public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
  348           handleInsert(changes.getOffset(), changes.getLength());
  349           super.insertUpdate(changes, a, f);
  350       }
  351   
  352       /**
  353        * Gives notification that something was removed from the document
  354        * in a location that this view is responsible for.  This is largely
  355        * delegated to the superclass, but is reimplemented to update the
  356        * relevant zones (i.e. determine if zones need to be removed or
  357        * joined with another zone).
  358        *
  359        * @param changes the change information from the associated document
  360        * @param a the current allocation of the view
  361        * @param f the factory to use to rebuild if the view has children
  362        * @see View#removeUpdate
  363        */
  364       public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
  365           handleRemove(changes.getOffset(), changes.getLength());
  366           super.removeUpdate(changes, a, f);
  367       }
  368   
  369       /**
  370        * Internally created view that has the purpose of holding
  371        * the views that represent the children of the ZoneView
  372        * that have been arranged in a zone.
  373        */
  374       class Zone extends AsyncBoxView {
  375   
  376           private Position start;
  377           private Position end;
  378   
  379           public Zone(Element elem, Position start, Position end) {
  380               super(elem, ZoneView.this.getAxis());
  381               this.start = start;
  382               this.end = end;
  383           }
  384   
  385           /**
  386            * Creates the child views and populates the
  387            * zone with them.  This is done by translating
  388            * the positions to child element index locations
  389            * and building views to those elements.  If the
  390            * zone is already loaded, this does nothing.
  391            */
  392           public void load() {
  393               if (! isLoaded()) {
  394                   setEstimatedMajorSpan(true);
  395                   Element e = getElement();
  396                   ViewFactory f = getViewFactory();
  397                   int index0 = e.getElementIndex(getStartOffset());
  398                   int index1 = e.getElementIndex(getEndOffset());
  399                   View[] added = new View[index1 - index0 + 1];
  400                   for (int i = index0; i <= index1; i++) {
  401                       added[i - index0] = f.create(e.getElement(i));
  402                   }
  403                   replace(0, 0, added);
  404   
  405                   zoneWasLoaded(this);
  406               }
  407           }
  408   
  409           /**
  410            * Removes the child views and returns to a
  411            * state of unloaded.
  412            */
  413           public void unload() {
  414               setEstimatedMajorSpan(true);
  415               removeAll();
  416           }
  417   
  418           /**
  419            * Determines if the zone is in the loaded state
  420            * or not.
  421            */
  422           public boolean isLoaded() {
  423               return (getViewCount() != 0);
  424           }
  425   
  426           /**
  427            * This method is reimplemented to not build the children
  428            * since the children are created when the zone is loaded
  429            * rather then when it is placed in the view hierarchy.
  430            * The major span is estimated at this point by building
  431            * the first child (but not storing it), and calling
  432            * setEstimatedMajorSpan(true) followed by setSpan for
  433            * the major axis with the estimated span.
  434            */
  435           protected void loadChildren(ViewFactory f) {
  436               // mark the major span as estimated
  437               setEstimatedMajorSpan(true);
  438   
  439               // estimate the span
  440               Element elem = getElement();
  441               int index0 = elem.getElementIndex(getStartOffset());
  442               int index1 = elem.getElementIndex(getEndOffset());
  443               int nChildren = index1 - index0;
  444   
  445               // replace this with something real
  446               //setSpan(getMajorAxis(), nChildren * 10);
  447   
  448               View first = f.create(elem.getElement(index0));
  449               first.setParent(this);
  450               float w = first.getPreferredSpan(X_AXIS);
  451               float h = first.getPreferredSpan(Y_AXIS);
  452               if (getMajorAxis() == X_AXIS) {
  453                   w *= nChildren;
  454               } else {
  455                   h += nChildren;
  456               }
  457   
  458               setSize(w, h);
  459           }
  460   
  461           /**
  462            * Publish the changes in preferences upward to the parent
  463            * view.
  464            * <p>
  465            * This is reimplemented to stop the superclass behavior
  466            * if the zone has not yet been loaded.  If the zone is
  467            * unloaded for example, the last seen major span is the
  468            * best estimate and a calculated span for no children
  469            * is undesirable.
  470            */
  471           protected void flushRequirementChanges() {
  472               if (isLoaded()) {
  473                   super.flushRequirementChanges();
  474               }
  475           }
  476   
  477           /**
  478            * Returns the child view index representing the given position in
  479            * the model.  Since the zone contains a cluster of the overall
  480            * set of child elements, we can determine the index fairly
  481            * quickly from the model by subtracting the index of the
  482            * start offset from the index of the position given.
  483            *
  484            * @param pos the position >= 0
  485            * @return  index of the view representing the given position, or
  486            *   -1 if no view represents that position
  487            * @since 1.3
  488            */
  489           public int getViewIndex(int pos, Position.Bias b) {
  490               boolean isBackward = (b == Position.Bias.Backward);
  491               pos = (isBackward) ? Math.max(0, pos - 1) : pos;
  492               Element elem = getElement();
  493               int index1 = elem.getElementIndex(pos);
  494               int index0 = elem.getElementIndex(getStartOffset());
  495               return index1 - index0;
  496           }
  497   
  498           protected boolean updateChildren(DocumentEvent.ElementChange ec,
  499                                            DocumentEvent e, ViewFactory f) {
  500               // the structure of this element changed.
  501               Element[] removedElems = ec.getChildrenRemoved();
  502               Element[] addedElems = ec.getChildrenAdded();
  503               Element elem = getElement();
  504               int index0 = elem.getElementIndex(getStartOffset());
  505               int index1 = elem.getElementIndex(getEndOffset()-1);
  506               int index = ec.getIndex();
  507               if ((index >= index0) && (index <= index1)) {
  508                   // The change is in this zone
  509                   int replaceIndex = index - index0;
  510                   int nadd = Math.min(index1 - index0 + 1, addedElems.length);
  511                   int nremove = Math.min(index1 - index0 + 1, removedElems.length);
  512                   View[] added = new View[nadd];
  513                   for (int i = 0; i < nadd; i++) {
  514                       added[i] = f.create(addedElems[i]);
  515                   }
  516                   replace(replaceIndex, nremove, added);
  517               }
  518               return true;
  519           }
  520   
  521           // --- View methods ----------------------------------
  522   
  523           /**
  524            * Fetches the attributes to use when rendering.  This view
  525            * isn't directly responsible for an element so it returns
  526            * the outer classes attributes.
  527            */
  528           public AttributeSet getAttributes() {
  529               return ZoneView.this.getAttributes();
  530           }
  531   
  532           /**
  533            * Renders using the given rendering surface and area on that
  534            * surface.  This is implemented to load the zone if its not
  535            * already loaded, and then perform the superclass behavior.
  536            *
  537            * @param g the rendering surface to use
  538            * @param a the allocated region to render into
  539            * @see View#paint
  540            */
  541           public void paint(Graphics g, Shape a) {
  542               load();
  543               super.paint(g, a);
  544           }
  545   
  546           /**
  547            * Provides a mapping from the view coordinate space to the logical
  548            * coordinate space of the model.  This is implemented to first
  549            * make sure the zone is loaded before providing the superclass
  550            * behavior.
  551            *
  552            * @param x   x coordinate of the view location to convert >= 0
  553            * @param y   y coordinate of the view location to convert >= 0
  554            * @param a the allocated region to render into
  555            * @return the location within the model that best represents the
  556            *  given point in the view >= 0
  557            * @see View#viewToModel
  558            */
  559           public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
  560               load();
  561               return super.viewToModel(x, y, a, bias);
  562           }
  563   
  564           /**
  565            * Provides a mapping from the document model coordinate space
  566            * to the coordinate space of the view mapped to it.  This is
  567            * implemented to provide the superclass behavior after first
  568            * making sure the zone is loaded (The zone must be loaded to
  569            * make this calculation).
  570            *
  571            * @param pos the position to convert
  572            * @param a the allocated region to render into
  573            * @return the bounding box of the given position
  574            * @exception BadLocationException  if the given position does not represent a
  575            *   valid location in the associated document
  576            * @see View#modelToView
  577            */
  578           public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
  579               load();
  580               return super.modelToView(pos, a, b);
  581           }
  582   
  583           /**
  584            * Start of the zones range.
  585            *
  586            * @see View#getStartOffset
  587            */
  588           public int getStartOffset() {
  589               return start.getOffset();
  590           }
  591   
  592           /**
  593            * End of the zones range.
  594            */
  595           public int getEndOffset() {
  596               return end.getOffset();
  597           }
  598   
  599           /**
  600            * Gives notification that something was inserted into
  601            * the document in a location that this view is responsible for.
  602            * If the zone has been loaded, the superclass behavior is
  603            * invoked, otherwise this does nothing.
  604            *
  605            * @param e the change information from the associated document
  606            * @param a the current allocation of the view
  607            * @param f the factory to use to rebuild if the view has children
  608            * @see View#insertUpdate
  609            */
  610           public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  611               if (isLoaded()) {
  612                   super.insertUpdate(e, a, f);
  613               }
  614           }
  615   
  616           /**
  617            * Gives notification that something was removed from the document
  618            * in a location that this view is responsible for.
  619            * If the zone has been loaded, the superclass behavior is
  620            * invoked, otherwise this does nothing.
  621            *
  622            * @param e the change information from the associated document
  623            * @param a the current allocation of the view
  624            * @param f the factory to use to rebuild if the view has children
  625            * @see View#removeUpdate
  626            */
  627           public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  628               if (isLoaded()) {
  629                   super.removeUpdate(e, a, f);
  630               }
  631           }
  632   
  633           /**
  634            * Gives notification from the document that attributes were changed
  635            * in a location that this view is responsible for.
  636            * If the zone has been loaded, the superclass behavior is
  637            * invoked, otherwise this does nothing.
  638            *
  639            * @param e the change information from the associated document
  640            * @param a the current allocation of the view
  641            * @param f the factory to use to rebuild if the view has children
  642            * @see View#removeUpdate
  643            */
  644           public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  645               if (isLoaded()) {
  646                   super.changedUpdate(e, a, f);
  647               }
  648           }
  649   
  650       }
  651   }

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