651 lines · 23.8 KB · Java
RawDownload
/* * Copyright (c) 1998, 2008, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */package javax.swing.text; import java.util.Vector;import java.awt.*;import javax.swing.event.*; /** * ZoneView is a View implementation that creates zones for which * the child views are not created or stored until they are needed * for display or model/view translations.  This enables a substantial * reduction in memory consumption for situations where the model * being represented is very large, by building view objects only for * the region being actively viewed/edited.  The size of the children * can be estimated in some way, or calculated asynchronously with * only the result being saved. * <p> * ZoneView extends BoxView to provide a box that implements * zones for its children.  The zones are special View implementations * (the children of an instance of this class) that represent only a * portion of the model that an instance of ZoneView is responsible * for.  The zones don't create child views until an attempt is made * to display them. A box shaped view is well suited to this because: *   <ul> *   <li> *   Boxes are a heavily used view, and having a box that *   provides this behavior gives substantial opportunity *   to plug the behavior into a view hierarchy from the *   view factory. *   <li> *   Boxes are tiled in one direction, so it is easy to *   divide them into zones in a reliable way. *   <li> *   Boxes typically have a simple relationship to the model (i.e. they *   create child views that directly represent the child elements). *   <li> *   Boxes are easier to estimate the size of than some other shapes. *   </ul> * <p> * The default behavior is controled by two properties, maxZoneSize * and maxZonesLoaded.  Setting maxZoneSize to Integer.MAX_VALUE would * have the effect of causing only one zone to be created.  This would * effectively turn the view into an implementation of the decorator * pattern.  Setting maxZonesLoaded to a value of Integer.MAX_VALUE would * cause zones to never be unloaded.  For simplicity, zones are created on * boundaries represented by the child elements of the element the view is * responsible for.  The zones can be any View implementation, but the * default implementation is based upon AsyncBoxView which supports fairly * large zones efficiently. * * @author  Timothy Prinzing * @see     View * @since   1.3 */public class ZoneView extends BoxView {     int maxZoneSize = 8 * 1024;    int maxZonesLoaded = 3;    Vector<View> loadedZones;     /**     * Constructs a ZoneView.     *     * @param elem the element this view is responsible for     * @param axis either View.X_AXIS or View.Y_AXIS     */    public ZoneView(Element elem, int axis) {        super(elem, axis);        loadedZones = new Vector<View>();    }     /**     * Get the current maximum zone size.     */    public int getMaximumZoneSize() {        return maxZoneSize;    }     /**     * Set the desired maximum zone size.  A     * zone may get larger than this size if     * a single child view is larger than this     * size since zones are formed on child view     * boundaries.     *     * @param size the number of characters the zone     * may represent before attempting to break     * the zone into a smaller size.     */    public void setMaximumZoneSize(int size) {        maxZoneSize = size;    }     /**     * Get the current setting of the number of zones     * allowed to be loaded at the same time.     */    public int getMaxZonesLoaded() {        return maxZonesLoaded;    }     /**     * Sets the current setting of the number of zones     * allowed to be loaded at the same time. This will throw an     * <code>IllegalArgumentException</code> if <code>mzl</code> is less     * than 1.     *     * @param mzl the desired maximum number of zones     *  to be actively loaded, must be greater than 0     * @exception IllegalArgumentException if <code>mzl</code> is < 1     */    public void setMaxZonesLoaded(int mzl) {        if (mzl < 1) {            throw new IllegalArgumentException("ZoneView.setMaxZonesLoaded must be greater than 0.");        }        maxZonesLoaded = mzl;        unloadOldZones();    }     /**     * Called by a zone when it gets loaded.  This happens when     * an attempt is made to display or perform a model/view     * translation on a zone that was in an unloaded state.     * This is imlemented to check if the maximum number of     * zones was reached and to unload the oldest zone if so.     *     * @param zone the child view that was just loaded.     */    protected void zoneWasLoaded(View zone) {        //System.out.println("loading: " + zone.getStartOffset() + "," + zone.getEndOffset());        loadedZones.addElement(zone);        unloadOldZones();    }     void unloadOldZones() {        while (loadedZones.size() > getMaxZonesLoaded()) {            View zone = loadedZones.elementAt(0);            loadedZones.removeElementAt(0);            unloadZone(zone);        }    }     /**     * Unload a zone (Convert the zone to its memory saving state).     * The zones are expected to represent a subset of the     * child elements of the element this view is responsible for.     * Therefore, the default implementation is to simple remove     * all the children.     *     * @param zone the child view desired to be set to an     *  unloaded state.     */    protected void unloadZone(View zone) {        //System.out.println("unloading: " + zone.getStartOffset() + "," + zone.getEndOffset());        zone.removeAll();    }     /**     * Determine if a zone is in the loaded state.     * The zones are expected to represent a subset of the     * child elements of the element this view is responsible for.     * Therefore, the default implementation is to return     * true if the view has children.     */    protected boolean isZoneLoaded(View zone) {        return (zone.getViewCount() > 0);    }     /**     * Create a view to represent a zone for the given     * range within the model (which should be within     * the range of this objects responsibility).  This     * is called by the zone management logic to create     * new zones.  Subclasses can provide a different     * implementation for a zone by changing this method.     *     * @param p0 the start of the desired zone.  This should     *  be >= getStartOffset() and < getEndOffset().  This     *  value should also be < p1.     * @param p1 the end of the desired zone.  This should     *  be > getStartOffset() and <= getEndOffset().  This     *  value should also be > p0.     */    protected View createZone(int p0, int p1) {        Document doc = getDocument();        View zone;        try {            zone = new Zone(getElement(),                            doc.createPosition(p0),                            doc.createPosition(p1));        } catch (BadLocationException ble) {            // this should puke in some way.            throw new StateInvariantError(ble.getMessage());        }        return zone;    }     /**     * Loads all of the children to initialize the view.     * This is called by the <code>setParent</code> method.     * This is reimplemented to not load any children directly     * (as they are created by the zones).  This method creates     * the initial set of zones.  Zones don't actually get     * populated however until an attempt is made to display     * them or to do model/view coordinate translation.     *     * @param f the view factory     */    protected void loadChildren(ViewFactory f) {        // build the first zone.        Document doc = getDocument();        int offs0 = getStartOffset();        int offs1 = getEndOffset();        append(createZone(offs0, offs1));        handleInsert(offs0, offs1 - offs0);    }     /**     * Returns the child view index representing the given position in     * the model.     *     * @param pos the position >= 0     * @return  index of the view representing the given position, or     *   -1 if no view represents that position     */    protected int getViewIndexAtPosition(int pos) {        // PENDING(prinz) this could be done as a binary        // search, and probably should be.        int n = getViewCount();        if (pos == getEndOffset()) {            return n - 1;        }        for(int i = 0; i < n; i++) {            View v = getView(i);            if(pos >= v.getStartOffset() &&               pos < v.getEndOffset()) {                return i;            }        }        return -1;    }     void handleInsert(int pos, int length) {        int index = getViewIndex(pos, Position.Bias.Forward);        View v = getView(index);        int offs0 = v.getStartOffset();        int offs1 = v.getEndOffset();        if ((offs1 - offs0) > maxZoneSize) {            splitZone(index, offs0, offs1);        }    }     void handleRemove(int pos, int length) {        // IMPLEMENT    }     /**     * Break up the zone at the given index into pieces     * of an acceptable size.     */    void splitZone(int index, int offs0, int offs1) {        // divide the old zone into a new set of bins        Element elem = getElement();        Document doc = elem.getDocument();        Vector<View> zones = new Vector<View>();        int offs = offs0;        do {            offs0 = offs;            offs = Math.min(getDesiredZoneEnd(offs0), offs1);            zones.addElement(createZone(offs0, offs));        } while (offs < offs1);        View oldZone = getView(index);        View[] newZones = new View[zones.size()];        zones.copyInto(newZones);        replace(index, 1, newZones);    }     /**     * Returns the zone position to use for the     * end of a zone that starts at the given     * position.  By default this returns something     * close to half the max zone size.     */    int getDesiredZoneEnd(int pos) {        Element elem = getElement();        int index = elem.getElementIndex(pos + (maxZoneSize / 2));        Element child = elem.getElement(index);        int offs0 = child.getStartOffset();        int offs1 = child.getEndOffset();        if ((offs1 - pos) > maxZoneSize) {            if (offs0 > pos) {                return offs0;            }        }        return offs1;    }     // ---- View methods ----------------------------------------------------     /**     * The superclass behavior will try to update the child views     * which is not desired in this case, since the children are     * zones and not directly effected by the changes to the     * associated element.  This is reimplemented to do nothing     * and return false.     */    protected boolean updateChildren(DocumentEvent.ElementChange ec,                                     DocumentEvent e, ViewFactory f) {        return false;    }     /**     * Gives notification that something was inserted into the document     * in a location that this view is responsible for.  This is largely     * delegated to the superclass, but is reimplemented to update the     * relevant zone (i.e. determine if a zone needs to be split into a     * set of 2 or more zones).     *     * @param changes the change information from the associated document     * @param a the current allocation of the view     * @param f the factory to use to rebuild if the view has children     * @see View#insertUpdate     */    public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f) {        handleInsert(changes.getOffset(), changes.getLength());        super.insertUpdate(changes, a, f);    }     /**     * Gives notification that something was removed from the document     * in a location that this view is responsible for.  This is largely     * delegated to the superclass, but is reimplemented to update the     * relevant zones (i.e. determine if zones need to be removed or     * joined with another zone).     *     * @param changes the change information from the associated document     * @param a the current allocation of the view     * @param f the factory to use to rebuild if the view has children     * @see View#removeUpdate     */    public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f) {        handleRemove(changes.getOffset(), changes.getLength());        super.removeUpdate(changes, a, f);    }     /**     * Internally created view that has the purpose of holding     * the views that represent the children of the ZoneView     * that have been arranged in a zone.     */    class Zone extends AsyncBoxView {         private Position start;        private Position end;         public Zone(Element elem, Position start, Position end) {            super(elem, ZoneView.this.getAxis());            this.start = start;            this.end = end;        }         /**         * Creates the child views and populates the         * zone with them.  This is done by translating         * the positions to child element index locations         * and building views to those elements.  If the         * zone is already loaded, this does nothing.         */        public void load() {            if (! isLoaded()) {                setEstimatedMajorSpan(true);                Element e = getElement();                ViewFactory f = getViewFactory();                int index0 = e.getElementIndex(getStartOffset());                int index1 = e.getElementIndex(getEndOffset());                View[] added = new View[index1 - index0 + 1];                for (int i = index0; i <= index1; i++) {                    added[i - index0] = f.create(e.getElement(i));                }                replace(0, 0, added);                 zoneWasLoaded(this);            }        }         /**         * Removes the child views and returns to a         * state of unloaded.         */        public void unload() {            setEstimatedMajorSpan(true);            removeAll();        }         /**         * Determines if the zone is in the loaded state         * or not.         */        public boolean isLoaded() {            return (getViewCount() != 0);        }         /**         * This method is reimplemented to not build the children         * since the children are created when the zone is loaded         * rather then when it is placed in the view hierarchy.         * The major span is estimated at this point by building         * the first child (but not storing it), and calling         * setEstimatedMajorSpan(true) followed by setSpan for         * the major axis with the estimated span.         */        protected void loadChildren(ViewFactory f) {            // mark the major span as estimated            setEstimatedMajorSpan(true);             // estimate the span            Element elem = getElement();            int index0 = elem.getElementIndex(getStartOffset());            int index1 = elem.getElementIndex(getEndOffset());            int nChildren = index1 - index0;             // replace this with something real            //setSpan(getMajorAxis(), nChildren * 10);             View first = f.create(elem.getElement(index0));            first.setParent(this);            float w = first.getPreferredSpan(X_AXIS);            float h = first.getPreferredSpan(Y_AXIS);            if (getMajorAxis() == X_AXIS) {                w *= nChildren;            } else {                h += nChildren;            }             setSize(w, h);        }         /**         * Publish the changes in preferences upward to the parent         * view.         * <p>         * This is reimplemented to stop the superclass behavior         * if the zone has not yet been loaded.  If the zone is         * unloaded for example, the last seen major span is the         * best estimate and a calculated span for no children         * is undesirable.         */        protected void flushRequirementChanges() {            if (isLoaded()) {                super.flushRequirementChanges();            }        }         /**         * Returns the child view index representing the given position in         * the model.  Since the zone contains a cluster of the overall         * set of child elements, we can determine the index fairly         * quickly from the model by subtracting the index of the         * start offset from the index of the position given.         *         * @param pos the position >= 0         * @return  index of the view representing the given position, or         *   -1 if no view represents that position         * @since 1.3         */        public int getViewIndex(int pos, Position.Bias b) {            boolean isBackward = (b == Position.Bias.Backward);            pos = (isBackward) ? Math.max(0, pos - 1) : pos;            Element elem = getElement();            int index1 = elem.getElementIndex(pos);            int index0 = elem.getElementIndex(getStartOffset());            return index1 - index0;        }         protected boolean updateChildren(DocumentEvent.ElementChange ec,                                         DocumentEvent e, ViewFactory f) {            // the structure of this element changed.

Lines 1–500 of 651.