ZoomPane changes for revisions 1094:1188

Translation of comments from French to English. The LINDA UP TO HERE comment suggests that translations of previous lines (not shown in this commit) were also done by someone else than the committer. This commit contains also some code reformatting, but the reformatting part does not apply to Apache SIS (code in SIS has been reformatted again).

The translations are not works that we can port to Apache SIS as-is. However the original work – the sentences in French – can be ported to Apache SIS without problems. In the code committed in Apache repository, we retranslated some sentences from the original French sentences. Some other sentences have only been reworded. I did that when the sentence was similar to what I would have written if I was redoing the translation from French anyway, but with sentences written in a more "javadoc-like" way and with some terms replaced by more standard words used in Java (e.g. "redefine" → "override", "display" → "show", etc.). The sentences that I did not changed are sentences that I would have written in approximately the same way anyway if I was redoing the translation from French.

As of December 2020, this code is not committed in the main Apache SIS branch. Instead we create a separated branch called "visual test" for now, with no intent to bring it back to main branch. The reason is that we do not plan to provide Swing application in Apache SIS; instead we are developing a JavaFX application. This Swing component is ported only as a debugging tool.

In this process I have learn a new English word: "whilst".

svn diff --extensions "--unified --ignore-space-change --ignore-all-space --ignore-eol-style" -r1094:1188 https://svn.osgeo.org/geotools/trunk/modules/extension/widgets-swing/src/main/java/org/geotools/gui/swing/ZoomPane.java
Revision 1094Revision 1188
* by the user through the scrollbars will be translated by calls to
* {@link #transform}.</p>
*
* @version 1.0
* @author Martin Desruisseaux
*/
* by the user through the scrollbars will be translated by calls to
* {@link #transform}.</p>
*
* $Id: ZoomPane.java,v 1.4 2002/07/22 09:24:18 jmacgill Exp $
* @version 1.0
* @author Martin Desruisseaux
*/
 * @version 1.0
 * @author Martin Desruisseaux
 */
private final class Listeners extends MouseAdapter implements MouseWheelListener, ComponentListener, Serializable
{
    public void mouseWheelMoved (final MouseWheelEvent event) {ZoomPane.this.mouseWheelMoved (event);}
    public void mousePressed    (final MouseEvent      event) {ZoomPane.this.mayShowPopupMenu(event);}
 * @version 1.0
 * @author Martin Desruisseaux
 */
private final class Listeners extends MouseAdapter implements MouseWheelListener,
                                                              ComponentListener, Serializable
{
    public void mouseWheelMoved (final MouseWheelEvent event) {ZoomPane.this.mouseWheelMoved (event);}
    public void mousePressed    (final MouseEvent      event) {ZoomPane.this.mayShowPopupMenu(event);}
        }
        double m=amount;
        if (button || (event.getModifiers() & ActionEvent.SHIFT_MASK)!=0) {
            if ((actionType & UNIFORM_SCALE)!=0) m = (m>=1) ? 2.0 : 0.5;
            else                                 m*= ENHANCEMENT_FACTOR;
        }
        transform(actionType & type, m, point);
    }
};
        }
        double m=amount;
        if (button || (event.getModifiers() & ActionEvent.SHIFT_MASK)!=0) {
            if ((actionType & UNIFORM_SCALE) != 0) {
                m = (m >= 1) ? 2.0 : 0.5;
        }
            else {
                m *= ENHANCEMENT_FACTOR;
            }
        }
        transform(actionType & type, m, point);
    }
};
    unexpectedException("reset", exception);
    return;
}
if (yAxisUpward) zoom.setToScale(+1, -1);
else             zoom.setToIdentity();
final AffineTransform transform=setVisibleArea(preferredArea,
                                               zoomableBounds);
change.concatenate(zoom);
    unexpectedException("reset", exception);
    return;
}
if (yAxisUpward) {
    zoom.setToScale(+1, -1);
}
else {
    zoom.setToIdentity();
}
final AffineTransform transform=setVisibleArea(preferredArea,
                                               zoomableBounds);
change.concatenate(zoom);
    visible=XAffineTransform.inverseTransform(zoom, zoomableBounds, null);
} catch (NoninvertibleTransformException exception) {
    unexpectedException("getVisibleArea", exception);
    visible=new Rectangle2D.Double(zoomableBounds.getCenterX(), zoomableBounds.getCenterY(), 0, 0);
}
visibleArea.setRect(visible);
return visible;
    visible=XAffineTransform.inverseTransform(zoom, zoomableBounds, null);
} catch (NoninvertibleTransformException exception) {
    unexpectedException("getVisibleArea", exception);
    visible = new Rectangle2D.Double(zoomableBounds.getCenterX(),
                                     zoomableBounds.getCenterY(), 0, 0);
}
visibleArea.setRect(visible);
return visible;
 * @param  logicalBounds Logical coordinates of the region to be displayed.
 * @throws IllegalArgumentException if <code>source</code> is empty.
 */
public final void setVisibleArea(final Rectangle2D logicalBounds) throws IllegalArgumentException {
    log("setVisibleArea", logicalBounds);
    transform(setVisibleArea(logicalBounds, getZoomableBounds()));
}
 * @param  logicalBounds Logical coordinates of the region to be displayed.
 * @throws IllegalArgumentException if <code>source</code> is empty.
 */
public void setVisibleArea(final Rectangle2D logicalBounds)
       throws IllegalArgumentException {
    log("setVisibleArea", logicalBounds);
    transform(setVisibleArea(logicalBounds, getZoomableBounds()));
}
 * @return Change to apply to the affine transform {@link #zoom}.
 * @throws IllegalArgumentException if <code>source</code> is empty.
 */
private AffineTransform setVisibleArea(Rectangle2D source, Rectangle2D dest) throws IllegalArgumentException {
    /*
     * Verifies the validity of the rectangle <code>source</code>. An
     * invalid rectangle will be rejected. However, we will be more
 * @return Change to apply to the affine transform {@link #zoom}.
 * @throws IllegalArgumentException if <code>source</code> is empty.
 */
private AffineTransform setVisibleArea(Rectangle2D source, Rectangle2D dest)
                                       throws IllegalArgumentException {
    /*
     * Verifies the validity of the rectangle <code>source</code>. An
     * invalid rectangle will be rejected. However, we will be more
if ((type & UNIFORM_SCALE) == UNIFORM_SCALE) {
    if (fillPanel)
    {
             if (sy*sourceWidth  > destWidth ) sx=sy;
        else if (sx*sourceHeight > destHeight) sy=sx;
    }
    else
    {
             if (sy*sourceWidth  < destWidth ) sx=sy;
        else if (sx*sourceHeight < destHeight) sy=sx;
    }
}
final AffineTransform change=AffineTransform.getTranslateInstance(
                 (type & TRANSLATE_X)!=0 ? dest.getCenterX()    : 0,
                 (type & TRANSLATE_Y)!=0 ? dest.getCenterY()    : 0);
if ((type & UNIFORM_SCALE) == UNIFORM_SCALE) {
    if (fillPanel)
    {
             if (sy * sourceWidth  > destWidth ) {
                 sx = sy;
    }
        else if (sx * sourceHeight > destHeight) {
            sy = sx;
        }
    }
    else
    {
             if (sy * sourceWidth  < destWidth ) {
                 sx=sy;
    }
        else if (sx * sourceHeight < destHeight) {
            sy=sx;
}
    }
}
final AffineTransform change=AffineTransform.getTranslateInstance(
                 (type & TRANSLATE_X)!=0 ? dest.getCenterX()    : 0,
                 (type & TRANSLATE_Y)!=0 ? dest.getCenterY()    : 0);
}

/**
 * LINDA UP TO HERE
 Change the {@linkplain #zoom} by applying and affine transform. The
 * <code>change</code> transform must express a change if logical units,
 * for example a translation in meters. This method is conceptually similar
 * to the following code:
 *
 * <pre>
 * {@link #zoom}.{@link AffineTransform#concatenate(AffineTransform) concatenate}(change);
}

/**
 * Changes the {@linkplain #zoom} by applying an affine transform. The
 * <code>change</code> transform must express a change in logical units,
 * for example, a translation in metres. This method is conceptually
 * similar to the following code:
 *
 * <pre>
 * {@link #zoom}.{@link AffineTransform#concatenate(AffineTransform) concatenate}(change);
 *
 * @param  change The zoom change, as an affine transform in logical
 *         coordinates. If <code>change</code> is the identity transform,
 *         then this method do nothing and listeners are not notified.
 */
public void transform(final AffineTransform change) {
    if (!change.isIdentity()) {
 *
 * @param  change The zoom change, as an affine transform in logical
 *         coordinates. If <code>change</code> is the identity transform,
 *         then this method does nothing and listeners are not notified.
 */
public void transform(final AffineTransform change) {
    if (!change.isIdentity()) {
}

/**
 * Effectue un zoom, une translation ou une rotation sur le contenu de
 * <code>ZoomPane</code>. Le type d'opération à effectuer dépend de
 * l'argument <code>operation</code>:
 *
 * <ul>
 *   <li>{@link #TRANSLATE_X} effectue une translation le long de l'axe des
 *       <var>x</var>. L'argument <code>amount</code> spécifie la
 *       transformation à effectuer en nombre de pixels. Une valeur négative
 *       déplace vers la gauche tandis qu'une valeur positive déplace vers
 *       la droite.</li>
 *   <li>{@link #TRANSLATE_Y} effectue une translation le long de l'axe des
 *       <var>y</var>. L'argument <code>amount</code> spécifie la
 *       transformation à effectuer en nombre de pixels. Une valeur négative
 *       déplace vers le haut tandis qu'une valeur positive déplace vers le
 *       bas.</li>
 *   <li>{@link #UNIFORM_SCALE} effectue un zoom. L'argument
 *       <code>zoom</code> spécifie le zoom à effectuer. Une valeur
 *       supérieure à 1 effectuera un zoom avant, tandis qu'une valeur
 *       comprise entre 0 et 1 effectuera un zoom arrière.</li>
 *   <li>{@link #ROTATE} effectue une rotation. L'argument <code>zoom</code>
 *       spécifie l'angle de rotation en radians.</li>
 *   <li>{@link #RESET} Redéfinit le zoom à une échelle, rotation et
 *       translation par défaut. Cette opération aura pour effet de faire
 *       apparaître la totalité ou quasi-totalité du contenu de
 *       <code>ZoomPane</code>.</li>
 *   <li>{@link #DEFAULT_ZOOM} Effectue un zoom par défaut, proche du zoom
 *       maximal, qui fait voir les détails du contenu de
 *       <code>ZoomPane</code> mais sans les grossir exégarément.</li>
 * </ul>
 *
 * @param  operation Type d'opération à effectuer.
 * @param  amount Translation en pixels ({@link #TRANSLATE_X} et
 *         {@link #TRANSLATE_Y}), facteur d'échelle ({@link #SCALE_X} et
 *         {@link #SCALE_Y}) ou angle de rotation en radians
 *         ({@link #ROTATE}). Dans les autres cas, cet argument est ignoré
 *         et peut être {@link Double#NaN}.
 * @param  center Centre du zoom ({@link #SCALE_X} et {@link #SCALE_Y}) ou
 *         de la rotation ({@link #ROTATE}), en coordonnées pixels. La
 *         valeur <code>null</code> désigne une valeur par défaut, le plus
 *         souvent le centre de la fenêtre.
 * @throws UnsupportedOperationException si l'argument
 *         <code>operation</code> n'est pas reconnu.
 */
private void transform(final int operation,
                       final double amount,
}

/**
 * Carries out a zoom, a translation or a rotation on the contents of
 * <code>ZoomPane</code>. The type of operation to carry out depends on the
 * <code>operation</code> argument:
 *
 * <ul>
 *   <li>{@link #TRANSLATE_X} carries out a translation along the
 *       <var>x</var> axis. The <code>amount</code> argument specifies the
 *       transformation to perform in number of pixels. A negative value
 *       moves to the left whilst a positive value moves to the right.</li>
 *   <li>{@link #TRANSLATE_Y} carries out a translation along the
 *       <var>y</var> axis. The <code>amount</code> argument specifies the
 *       transformation to perform in number of pixels. A negative value
 *       moves upwards whilst a positive value moves downwards.</li>
 *   <li>{@link #UNIFORM_SCALE} carries out a zoom. The <code>zoom</code>
 *       argument specifies the type of zoom to perform. A value greater
 *       than 1 will perform a zoom in whilst a value between 0 and 1 will
 *       perform a zoom out.</li>
 *   <li>{@link #ROTATE} carries out a rotation. The <code>zoom</code>
 *       argument specifies the rotation angle in radians.</li>
 *   <li>{@link #RESET} Redefines the zoom to a default scale, rotation
 *       and translation. This operation displays all, or almost all, the
 *       contents of <code>ZoomPane</code>.</li>
 *   <li>{@link #DEFAULT_ZOOM} Carries out a default zoom, close to the
 *       maximum zoom, which shows the details of the contents of
 *       <code>ZoomPane</code> but without enlarging them too much.</li>
 * </ul>
 *
 * @param  operation Type of operation to perform.
 * @param  amount ({@link #TRANSLATE_X} and
 *         {@link #TRANSLATE_Y}) translation in pixels, ({@link #SCALE_X}
 *         and {@link #SCALE_Y}) scale factor or ({@link #ROTATE}) rotation
 *         angle in radians.
 *         In other cases, this argument is ignored and can be
 *         {@link Double#NaN}.
 * @param  center Zoom centre ({@link #SCALE_X} and {@link #SCALE_Y}) or
 *         rotation centre ({@link #ROTATE}), in pixel coordinates. The
 *         value <code>null</code> indicates a default value, more often
 *         not the centre of the window.
 * @throws UnsupportedOperationException if the <code>operation</code>
 *         <code>operation</code> argument isn't recognized.
 */
private void transform(final int operation,
                       final double amount,
                     ((operation & TRANSLATE_Y)!=0) ? amount : 0);
} else {
    /*
     * Obtient les coordonnées (en pixels)
     * du centre de rotation ou du zoom.
     */
    final double centerX;
    final double centerY;
                     ((operation & TRANSLATE_Y)!=0) ? amount : 0);
} else {
    /*
     * Obtains the coordinates (in pixels) of the rotation or
     * zoom centre.
     */
    final double centerX;
    final double centerY;
        return;
    }
    /*
     * On accepte les largeurs et hauteurs de 0. Si toutefois le
     * rectangle n'est pas valide (largeur ou hauteur négatif),
     * alors on terminera cette méthode sans rien faire. Aucun
     * zoom n'aura été effectué.
     */
}
if ((operation & (ROTATE))!=0) {
        return;
    }
    /*
     * Zero lengths and widths are accepted.  If, however, the
     * rectangle isn't valid (negative length or width) then the
     * method will end without doing anything. No zoom will be
     * performed.
     */
}
if ((operation & (ROTATE))!=0) {
}

/**
 * Ajoute un objet à la liste des objets intéressés
 * à être informés des changements de zoom.
 */
public void addZoomChangeListener(final ZoomChangeListener listener) {
    listenerList.add(ZoomChangeListener.class, listener);
}

/**
 * Adds an object to the list of objects interested in being notified
 * about zoom changes.
 */
public void addZoomChangeListener(final ZoomChangeListener listener) {
    listenerList.add(ZoomChangeListener.class, listener);
}

/**
 * Retire un objet de la liste des objets intéressés
 * à être informés des changements de zoom.
 */
public void removeZoomChangeListener(final ZoomChangeListener listener) {
    listenerList.remove(ZoomChangeListener.class, listener);
}

/**
 * Removes an object from the list of objects interested in being notified
 * about zoom changes.
 */
public void removeZoomChangeListener(final ZoomChangeListener listener) {
    listenerList.remove(ZoomChangeListener.class, listener);
}

/**
 * Ajoute un objet à la liste des objets intéressés
 * à être informés des événements de la souris.
 */
public void addMouseListener(final MouseListener listener) {
    super.removeMouseListener(mouseSelectionTracker);
}

/**
 * Adds an object to the list of objects interested in being notified
 * about mouse events.
 */
public void addMouseListener(final MouseListener listener) {
    super.removeMouseListener(mouseSelectionTracker);
}

/**
 * Signale qu'un changement du zoom vient d'être effectué. Chaque objets
 * enregistrés par la méthode {@link #addZoomChangeListener} sera prévenu
 * du changement aussitôt que possible.
 *
 * @param change Transformation affine qui représente le changement dans le
 *               zoom. Soit <code>oldZoom</code> et <code>newZoom</code> les
 *               transformations affines de l'ancien et du nouveau zoom
 *               respectivement. Alors la relation
 *
 * <code>newZoom=oldZoom.{@link AffineTransform#concatenate concatenate}(change)</code>
 *
 *               doit être respectée (aux erreurs d'arrondissements près).
 *               <strong>Notez que cette méthode peut modifier
 *               <code>change</code></strong> pour combiner en une seule
 *               transformation plusieurs appels consécutifs de
 *               <code>fireZoomChanged</code>.
 */
protected void fireZoomChanged(final AffineTransform change) {
    visibleArea.setRect(getVisibleArea());
}

/**
 * Signals that a zoom change has taken place. Every object registered by
 * the {@link #addZoomChangeListener} method will be notified of the change
 * as soon as possible.
 *
 * @param change Affine transform which represents the change in the zoom.
 *               That is <code>oldZoom</code> and <code>newZoom</code> are
 *               the affine transforms of the old and new zoom respectively.
 *               Therefore, the relation
 * <code>newZoom=oldZoom.{@link AffineTransform#concatenate concatenate}(change)</code>
 *               must be respected (to within rounding errors).
 *               <strong>Note: This method can modify
 *               <code>change</code></strong> to combine several
 *               consecutive calls of
 *               <code>fireZoomChanged</code> in a single transformation.
 */
protected void fireZoomChanged(final AffineTransform change) {
    visibleArea.setRect(getVisibleArea());
}

/**
 * Préviens les classes dérivées que le zoom a changé. Contrairement à la
 * méthode {@link #fireZoomChanged} protégée, cette méthode privée ne
 * modifie aucun champ interne et n'essaye pas d'appeller d'autres méthodes
 * de <code>ZoomPane</code> comme {@link #getVisibleArea}. On évite ainsi
 * une boucle sans fin lorsque cette méthode est appelée par {@link #reset}.
 */
private void fireZoomChanged0(final AffineTransform change) {
    /*
     * Note: il faut lancer l'événement même si la transformation
     *       est la matrice identité, car certaine classe utilise
     *       ce truc pour mettre à jour les barres de défilements.
     */
    if (change==null) {
        throw new NullPointerException();
}

/**
 * Notifies derived classes that the zoom has changed. Unlike the
 * protected {@link #fireZoomChanged} method, this private method doesn't
 * modify any internal field and doesn't attempt to call other
 * <code>ZoomPane> methods such as {@link #getVisibleArea}. An infinite
 * loop is thereby avoided as this method is called by {@link #reset}.
 */
private void fireZoomChanged0(final AffineTransform change) {
    /*
     * Note: the event must be fired even if the transformation
     *       is the identity matrix, because certain classes use
     *       this to update scrollbars.
     */
    if (change==null) {
        throw new NullPointerException();
}

/**
 * Méthode appelée automatiquement après que l'utilisateur ait sélectionnée
 * une région à l'aide de la souris. L'implémentation par défaut zoom la
 * région <code>area</code> sélectionnée. Les classes dérivées peuvent
 * redéfinir cette méthode pour entreprendre une autre action.
 *
 * @param area Région sélectionnée par l'utilisateur, en coordonnées
 *        logiques.
 */
protected void mouseSelectionPerformed(final Shape area) {
    final Rectangle2D rect=(area instanceof Rectangle2D) ? (Rectangle2D) area : area.getBounds2D();
}

/**
 * Method called automatically after the user selects an area with the
 * mouse. The default implementation zooms to the selected
 * <code>area</code>. Derived classes can redefine this method in order
 * to carry out another action.
 *
 * @param area Area selected by the user, in logical coordinates.
 */
protected void mouseSelectionPerformed(final Shape area) {
    final Rectangle2D rect=(area instanceof Rectangle2D) ? (Rectangle2D) area : area.getBounds2D();
}

/**
 * Retourne la forme géométrique à utiliser pour délimiter une région.
 * Cette forme est généralement un rectangle mais pourrait aussi être
 * une ellipse, une flèche ou d'autres formes encore. Les coordonnées
 * de la forme retournée ne seront pas prises en compte. En fait, ces
 * coordonnées seront régulièrement écrasées.  Seule compte la classe
 * de la forme retournée (par exemple {@link java.awt.geom.Ellipse2D}
 * vs {@link java.awt.geom.Rectangle2D}) et ses paramètres non-reliés
 * à sa position (par exemple l'arrondissement des coins d'un rectangle
 * {@link java.awt.geom.RoundRectangle2D}).
 *
 * La forme retournée sera généralement d'une classe dérivée de
 * {@link RectangularShape}, mais peut aussi être de la classe
 * {@link Line2D}. <strong>Tout autre classe risque de lancer une
 * {@link ClassCastException} au moment de l'exécution</strong>.
 *
 * L'implémentation par défaut retourne toujours un objet
 * {@link java.awt.geom.Rectangle2D}.
 *
 * @param  event Coordonnées logiques de la souris au moment ou le bouton a
 *         été enfoncé. Cette information peut être utilisée par les classes
 *         dérivées qui voudraient tenir compte de la position de la souris
 *         avant de choisir une forme géométrique.
 * @return Forme de la classe {link RectangularShape} ou {link Line2D}, ou
 *         <code>null</code> pour indiquer qu'on ne veut pas faire de
 *         sélection avec la souris.
 */
protected Shape getMouseSelectionShape(final Point2D point) {
    return new Rectangle2D.Float();
}

/**
 * Returns the geometric shape to be used to delimitate an area.
 * This shape is generally a rectangle but could also be an ellipse,
 * an arrow or another shape. The coordinates of the returned shape
 * won't be taken into account. In fact, these coordinates will often
 * be destroyed. The only things which count are the class of the
 * returned shape (e.g. {@link java.awt.geom.Ellipse2D} vs
 * {@link java.awt.geom.Rectangle2D}) and any of its parameters not
 * related to its position (e.g. corner rounding in a rectangle
 * {@link java.awt.geom.RoundRectangle2D}).
 *
 * The returned shape will generally be from a class derived from
 * {@link RectangularShape}, but can also be from the class
 * {@link Line2D}. <strong>Any other class risks firing a
 * {@link ClassCastException} at execution</strong>.
 *
 * The default implementation always returns a
 * {@link java.awt.geom.Rectangle2D} object.
 *
 * @param  event Logical coordinates of the mouse at the moment the button
 *         is pressed. This information can be used by derived classes
 *         that wish to consider the mouse position before choosing a
 *         geometric shape.
 * @return Shape from the class {link RectangularShape} or {link Line2D},
 *         or <code>null</code> to indicate that we do not want to select
 *         with the mouse.
 */
protected Shape getMouseSelectionShape(final Point2D point) {
    return new Rectangle2D.Float();
}

/**
 * Indique si la loupe est visible. Par défaut, la loupe n'est pas visible.
 * Appelez {@link #setMagnifierVisible(boolean)} pour la faire apparaitre.
 */
public boolean isMagnifierVisible() {
    return magnifier!=null;
}

/**
 * Indicates whether or not the magnifying glass is visible.  By default,
 * it is not visible. Call {@link #setMagnifierVisible(boolean)} to make it
 * appear.
 */
public boolean isMagnifierVisible() {
    return magnifier!=null;
}

/**
 * Fait apparaître ou disparaître la loupe. Si la loupe n'était pas visible
 * et que cette méthode est appelée avec l'argument <code>true</code>, alors
 * la loupe apparaîtra au centre de la fenêtre.
 */
public void setMagnifierVisible(final boolean visible) {
    setMagnifierVisible(visible, null);
}

/**
 * Displays or hides the magnifying glass. If the magnifying glass is not
 * visible and this method is called with the argument <code>true</code>,
 * the magnifying glass will appear at the centre of the window.
 */
public void setMagnifierVisible(final boolean visible) {
    setMagnifierVisible(visible, null);
}

/**
 * Indique si l'affichage de la loupe est autorisée sur
 * cette composante. Par défaut, elle est autorisée.
 */
public boolean isMagnifierEnabled() {
    return magnifierEnabled;
}

/**
 * Indicates whether or not the magnifying glass is allowed to be
 * displayed on this component.  By default, it is allowed.
 */
public boolean isMagnifierEnabled() {
    return magnifierEnabled;
}

/**
 * Spécifie si l'affichage de la loupe est autorisée sur cette composante.
 * L'appel de cette méthode avec la valeur <code>false</code> fera
 * disparaître la loupe, supprimera le choix "Afficher la loupe" du menu
 * contextuel et fera ignorer tous les appels à
 * <code>{@link #setMagnifierVisible setMagnifierVisible}(true)</code>.
 */
public void setMagnifierEnabled(final boolean enabled) {
    magnifierEnabled=enabled;
}

/**
 * Specifies whether or not the magnifying glass is allowed to be displayed
 * on this component. Calling this method with the value <code>false</code>
 * will hide the magnifying glass, delete the choice "Display magnifying
 * glass" from the contextual menu and lead to all calls to
 * <code>{@link #setMagnifierVisible setMagnifierVisible}(true)</code>
 * being ignored.
 */
public void setMagnifierEnabled(final boolean enabled) {
    magnifierEnabled=enabled;
}

/**
 * Corrige les coordonnées d'un pixel pour tenir compte de la présence de la
 * loupe. La point <code>point</code> doit contenir les coordonnées d'un
 * pixel à l'écran. Si la loupe est visible et que <code>point</code> se
 * trouve sur cette loupe, alors ses coordonnées seront corrigées pour faire
 * comme si elle pointait sur le même pixel, mais en l'absence de la loupe.
 * En effet, la présence de la loupe peut déplacer la position apparante des
 * pixels.
 */
public final void correctPointForMagnifier(final Point2D point) {
    if (magnifier!=null && magnifier.contains(point)) {
}

/**
 * Corrects a pixel's coordinates to take into account the presence of the
 * magnifying glass. The point <code>point</code> must contain the
 * coordinates of a pixel on the screen. If the magnifying glass is visible
 * and <code>point</code> falls within it, point's coordinates will be
 * corrected to make as if it pointed at the pixel itself, but in the
 * absence of the magnifying glass. In effect, the presence of the
 * magnifying glass can move the apparent position of the pixels.
 */
public final void correctPointForMagnifier(final Point2D point) {
    if (magnifier!=null && magnifier.contains(point)) {
final double centerX = magnifier.getCenterX();
final double centerY = magnifier.getCenterY();
/*
 * Le code suivant est équivalent au transformations ci-dessous.
 * Ces transformations doivent être identiques à celles qui sont
 * appliquées dans {@link #paintMagnifier}.
 *
 *         translate(+centerX, +centerY);
 *         scale    (magnifierPower, magnifierPower);
final double centerX = magnifier.getCenterX();
final double centerY = magnifier.getCenterY();
/*
 * The following code is equivalent to the following
 * transformations.
 * These transformations must be identical to those which
 * are applied in {@link #paintMagnifier}.
 *
 *         translate(+centerX, +centerY);
 *         scale    (magnifierPower, magnifierPower);
}

/**
 * Fait apparaître ou disparaître la loupe. Si la loupe n'était pas visible
 * et que cette méthode est appelée avec l'argument <code>true</code>, alors
 * la loupe apparaîtra centré sur la coordonnées spécifiée.
 *
 * @param visible <code>true</code> pour faire apparaître la loupe,
 *                ou <code>false</code> pour la faire disparaître.
 * @param center  Coordonnée centrale à laquelle faire apparaître la loupe.
 *                Si la loupe était initialement invisible, elle apparaîtra
 *                centrée à cette coordonnée (ou au centre de l'écran si
 *                <code>center</code> est nul). Si la loupe était déjà
 *                visible et que <code>center</code> est non-nul, alors elle
 *                sera déplacée pour la centrer à la coordonnées spécifiée.
 */
private void setMagnifierVisible(final boolean visible, final Point center) {
    if (visible && magnifierEnabled) {
        if (magnifier==null) {
            Rectangle bounds=getZoomableBounds(); // Do not modifiy the Rectangle!
            if (bounds.isEmpty()) bounds=new Rectangle(0,0,DEFAULT_SIZE,DEFAULT_SIZE);
            final int size=Math.min(Math.min(bounds.width, bounds.height), DEFAULT_MAGNIFIER_SIZE);
            final int centerX, centerY;
}

/**
 * Displays or hides the magnifying glass. If the magnifying glass isn't
 * visible and this method is called with the argument <code>true</code>,
 * the magnifying glass will be displayed centred on the specified
 * coordinate.
 *
 * @param visible <code>true</code> to display the magnifying glass or
 *                <code>false</code> to hide it.
 * @param center  Central coordinate on which to display the magnifying
 *                glass.  If the magnifying glass was initially invisible,
 *                it will appear centred on this coordinate (or in the
 *                centre of the screen if <code>center</code> is null). If
 *                the magnifying glass was already visible and
 *                <code>center</code> is not null, it will be moved to
 *                centre it on the specified coordinate.
 */
private void setMagnifierVisible(final boolean visible, final Point center) {
    if (visible && magnifierEnabled) {
        if (magnifier==null) {
            Rectangle bounds = getZoomableBounds(); // Do not modify the Rectangle!
            if (bounds.isEmpty()) bounds=new Rectangle(0,0,DEFAULT_SIZE,DEFAULT_SIZE);
            final int size=Math.min(Math.min(bounds.width, bounds.height), DEFAULT_MAGNIFIER_SIZE);
            final int centerX, centerY;
}

/**
 * Ajoute au menu spécifié des options de navigations. Des menus
 * tels que "Zoom avant" et "Zoom arrière" seront automatiquement
 * ajoutés au menu avec les raccourcis-clavier appropriés.
 */
public void buildNavigationMenu(final JMenu menu) {
    buildNavigationMenu(menu, null);
}

/**
 * Adds navigation options to the specified menu. Menus such as
 * "Zoom in" and "Zoom out" will be automatically added to the menu
 * together with the appropriate short-cut keys.
 */
public void buildNavigationMenu(final JMenu menu) {
    buildNavigationMenu(menu, null);
}

/**
 * Ajoute au menu spécifié des options de navigations. Des menus
 * tels que "Zoom avant" et "Zoom arrière" seront automatiquement
 * ajoutés au menu avec les raccourcis-clavier appropriés.
 */
private void buildNavigationMenu(final JMenu menu, final JPopupMenu popup) {
    int groupIndex=0;
}

/**
 * Adds navigation options to the specified menu. Menus such as
 * "Zoom in" and "Zoom out" will be automatically added to the menu
 * together with the appropriate short-cut keys.
 */
private void buildNavigationMenu(final JMenu menu, final JPopupMenu popup) {
    int groupIndex=0;
final Action action=actionMap.get(ACTION_ID[i]);
if (action!=null && action.getValue(Action.NAME)!=null) {
    /*
     * Vérifie si le prochain item fait parti d'un nouveau groupe.
     * Si c'est la cas, il faudra ajouter un séparateur avant le
     * prochain menu.
     */
    final int lastGroupIndex=groupIndex;
    while ((ACTION_TYPE[i] & GROUP[groupIndex]) == 0) {
        groupIndex = (groupIndex+1) % GROUP.length;
        if (groupIndex==lastGroupIndex) break;
    }
    /*
     * Ajoute un item au menu.
     */
    if (menu!=null) {
        if (groupIndex!=lastGroupIndex) menu.addSeparator();
final Action action=actionMap.get(ACTION_ID[i]);
if (action!=null && action.getValue(Action.NAME)!=null) {
    /*
     * Checks whether the next item belongs to a new group.
     * If this is the case, it will be necessary to add a separator
     * before the next menu.
     */
    final int lastGroupIndex=groupIndex;
    while ((ACTION_TYPE[i] & GROUP[groupIndex]) == 0) {
        groupIndex = (groupIndex+1) % GROUP.length;
        if (groupIndex == lastGroupIndex) {
            break;
    }
    }
    /*
     * Adds an item to the menu.
     */
    if (menu!=null) {
        if (groupIndex!=lastGroupIndex) menu.addSeparator();
}

/**
 * Menu avec une position. Cette classe retient les coordonnées
 * exacte de l'endroit où a cliqué l'utilisateur lorsqu'il a
 * invoké ce menu.
 *
 * @author Martin Desruisseaux
 * @version 1.0
}

/**
 * Menu with a position.  This class retains the exact coordinates of the
 * place the user clicked when this menu was invoked.
 *
 * @author Martin Desruisseaux
 * @version 1.0
 */
private static final class PointPopupMenu extends JPopupMenu {
    /**
     * Coordonnées de l'endroit où
     * avait cliqué l'utilisateur.
     */
    public final Point point;

    /**
     * Construit un menu en retenant
     * la coordonnée spécifiée.
     */
    public PointPopupMenu(final Point point) {
        this.point=point;
 */
private static final class PointPopupMenu extends JPopupMenu {
    /**
     * Coordinates of the point the user clicked on.
     */
    public final Point point;

    /**
     * Constructs a menu, retaining the specified coordinate.
     */
    public PointPopupMenu(final Point point) {
        this.point=point;
}

/**
 * Méthode appelée automatiquement lorsque l'utilisateur a cliqué sur le
 * bouton droit de la souris. L'implémentation par défaut fait apparaître
 * un menu contextuel dans lequel figure des options de navigations.
 *
 * @param  event Evénement de la souris contenant entre autre les
 *         coordonnées pointées.
 * @return Le menu contextuel, ou <code>null</code> pour ne pas faire
 *         apparaître de menu.
 */
protected JPopupMenu getPopupMenu(final MouseEvent event) {
    if (getZoomableBounds().contains(event.getX(), event.getY())) {
}

/**
 * Method called automatically when the user clicks on the right mouse
 * button.  The default implementation displays a contextual menu
 * containing navigation options.
 *
 * @param  event Mouse event containing amongst others, the
 *         coordinates pointées???????????.
 * @return The contextual menu, or <code>null</code> to avoid displaying
 *         the menu.
 */
protected JPopupMenu getPopupMenu(final MouseEvent event) {
    if (getZoomableBounds().contains(event.getX(), event.getY())) {
}

/**
 * Méthode appelée automatiquement lorsque l'utilisateur a cliqué sur le
 * bouton droit de la souris à l'intérieur de la loupe. L'implémentation
 * par défaut fait apparaître un menu contextuel dans lequel figure des
 * options relatives à la loupe.
 *
 * @param  event Evénement de la souris contenant entre autre les
 *         oordonnées pointées.
 * @return Le menu contextuel, ou <code>null</code> pour ne pas faire
 *         apparaître de menu.
 */
protected JPopupMenu getMagnifierMenu(final MouseEvent event) {
    final Resources resources = Resources.getResources(getLocale());
}

/**
 * Method called automatically when the user clicks on the right mouse
 * button inside the magnifying glass. The default implementation displays
 * a contextual menu which contains magnifying glass options.
 *
 * @param  event Mouse event containing amongst others, the
 *         coordinates ???? pointées.
 * @return The contextual menu, or <code>null</code> to avoid displaying
 *         the menu.
 */
protected JPopupMenu getMagnifierMenu(final MouseEvent event) {
    final Resources resources = Resources.getResources(getLocale());
}

/**
 * Fait apparaître le menu contextuel de navigation, à la
 * condition que l'évènement de la souris soit bien celui
 * qui fait normalement apparaître ce menu.
 */
private void mayShowPopupMenu(final MouseEvent event) {
    if ( event.getID()       == MouseEvent.MOUSE_PRESSED &&
}

/**
 * Displays the navigation contextual menu, provided the mouse event is
 * in fact the one which normally displays this menu.
 */
private void mayShowPopupMenu(final MouseEvent event) {
    if ( event.getID()       == MouseEvent.MOUSE_PRESSED &&
    SwingUtilities.convertPointToScreen(point, source);
    screen.width  -= (size.width  + insets.right);
    screen.height -= (size.height + insets.bottom);
    if (point.x > screen.width)  point.x = screen.width;
    if (point.y > screen.height) point.y = screen.height;
    if (point.x < insets.left)   point.x = insets.left;
    if (point.y < insets.top)    point.y = insets.top;
    SwingUtilities.convertPointFromScreen(point, source);
    popup.show(source, point.x, point.y);
}
    SwingUtilities.convertPointToScreen(point, source);
    screen.width  -= (size.width  + insets.right);
    screen.height -= (size.height + insets.bottom);
    if (point.x > screen.width) {
        point.x = screen.width;
    }
    if (point.y > screen.height) {
        point.y = screen.height;
    }
    if (point.x < insets.left) {
        point.x = insets.left;
    }
    if (point.y < insets.top) {
        point.y = insets.top;
    }
    SwingUtilities.convertPointFromScreen(point, source);
    popup.show(source, point.x, point.y);
}
}

/**
 * Méthode appelée automatiquement lorsque l'utilisateur a fait
 * tourné la roulette de la souris. Cette méthode effectue un
 * zoom centré sur la position de la souris.
 */
private final void mouseWheelMoved(final MouseWheelEvent event)
{
}

/**
 * Method called automatically when user moves the mouse wheel. This method
 * performs a zoom centred on the mouse position.
 */
private final void mouseWheelMoved(final MouseWheelEvent event)
{
}

/**
 * Méthode appelée chaque fois que la dimension
 * ou la position de la composante a changée.
 */
private final void processSizeEvent(final ComponentEvent event)
{
}

/**
 * Method called each time the size or the position of the component
 * changes.
 */
private final void processSizeEvent(final ComponentEvent event)
{
    magnifier.setClip(getZoomableBounds());
}
/*
 * On n'appelle par {@link #repaint} parce qu'il y a déjà une commande
 * {@link #repaint} dans la queue.  Ainsi, le retraçage sera deux fois
 * plus rapide sous le JDK 1.3. On n'appele pas {@link #transform} non
 * plus car le zoom n'a pas vraiment changé;  on a seulement découvert
 * une partie de la fenêtre qui était cachée. Mais il faut tout de même
 * ajuster les barres de défilements.
 */
final Object[] listeners=listenerList.getListenerList();
for (int i=listeners.length; (i-=2)>=0;) {
    magnifier.setClip(getZoomableBounds());
}
/*
 * {@link #repaint} isn't called because there is already a
 * {@link #repaint} command in the queue.  Therefore, the redraw will
 * be twice as quick under JDK 1.3. {@link #transform} isn't called
 * either because the zoom hasn't really changed; we have simply
 * discovered a part of the window which was hidden before. However,
 * we still need to adjust the scrollbars.
 */
final Object[] listeners=listenerList.getListenerList();
for (int i=listeners.length; (i-=2)>=0;) {
}

/**
 * Retourne un objet qui affiche ce <code>ZoomPane</code>
 * avec des barres de défilements.
 */
public JComponent createScrollPane() {
    return new ScrollPane();
}

/**
 * Returns an object which displays this <code>ZoomPane</code>
 * with the scrollbars.
 */
public JComponent createScrollPane() {
    return new ScrollPane();
/**
 * The scroll panel for {@link ZoomPane}. The standard {@link JScrollPane}
 * class is not used because it is difficult to get {@link JViewport} to
 * cooperate with transformation already handled by {@link ZoomPane#zoom}.
 *
 * @version 1.0
 * @author Martin Desruisseaux
/**
 * The scroll panel for {@link ZoomPane}. The standard {@link JScrollPane}
 * class is not used because it is difficult to get {@link JViewport} to
 * cooperate with transformations already handled by {@link ZoomPane#zoom}.
 *
 * @version 1.0
 * @author Martin Desruisseaux
 */
private final class ScrollPane extends JComponent implements PropertyChangeListener {
    /**
     * The horizontal scrolling bar, or <code>null</code> if none.
     */
    private final JScrollBar scrollbarX;

    /**
     * The vertical scrolling bar, or <code>null</code> if none.
     */
    private final JScrollBar scrollbarY;

    /**
     * Construct a scroll pane for the enclosing {@link ZoomPane}.
     */
    public ScrollPane() {
        setOpaque(false);
        setLayout(new GridBagLayout());
        /*
         * Setup the scroll bars.
         */
        if ((type & TRANSLATE_X)!=0) {
            scrollbarX=new JScrollBar(JScrollBar.HORIZONTAL);
 */
private final class ScrollPane extends JComponent implements PropertyChangeListener {
    /**
     * The horizontal scrollbar, or <code>null</code> if none.
     */
    private final JScrollBar scrollbarX;

    /**
     * The vertical scrollbar, or <code>null</code> if none.
     */
    private final JScrollBar scrollbarY;

    /**
     * Constructs a scroll pane for the enclosing {@link ZoomPane}.
     */
    public ScrollPane() {
        setOpaque(false);
        setLayout(new GridBagLayout());
        /*
         * Sets up the scrollbars.
         */
        if ((type & TRANSLATE_X)!=0) {
            scrollbarX=new JScrollBar(JScrollBar.HORIZONTAL);
    scrollbarY  = null;
}
/*
 * Add the scroll bars in the scroll pane.
 */
final Rectangle bounds = getZoomableBounds(); // Cached Rectangle: do not modify.
final GridBagConstraints c = new GridBagConstraints();
    scrollbarY  = null;
}
/*
 * Adds the scrollbars in the scroll pane.
 */
final Rectangle bounds = getZoomableBounds(); // Cached Rectangle: do not modify.
final GridBagConstraints c = new GridBagConstraints();
}

/**
 * Convenience method fetching a scroll bar model.
 * Should be a static method, but compiler doesn't
 * allow this.
 */
}

/**
 * Convenience method which fetches a scrollbar model.
 * Should be a static method, but compiler doesn't
 * allow this.
 */
/**
 * Invoked when this <code>ScrollPane</code>  is added in
 * a {@link Container}. This method register all required
 * listeners.
 */
public void addNotify() {
/**
 * Invoked when this <code>ScrollPane</code>  is added in
 * a {@link Container}. This method registers all required
 * listeners.
 */
public void addNotify() {
/**
 * Invoked when this <code>ScrollPane</code> is removed from
 * a {@link Container}. This method unregister all listeners.
 */
public void removeNotify() {
    ZoomPane.this.removePropertyChangeListener("zoom.insets", this);
/**
 * Invoked when this <code>ScrollPane</code> is removed from
 * a {@link Container}. This method unregisters all listeners.
 */
public void removeNotify() {
    ZoomPane.this.removePropertyChangeListener("zoom.insets", this);
}

/**
 * Invoked when the zoomable area changed. This method will adjust
 * scroll bar's insets in order to keep scroll bars aligned in front
 * of the zoomable area.
 *
 * Note: in current version, this is an undocumented capability.
 *       Class {@link RangeBar} use it, but it is experimental.
 *       It may change in a future version.
 */
public void propertyChange(final PropertyChangeEvent event) {
}

/**
 * Invoked when the zoomable area changes. This method will adjust
 * scroll bar's insets in order to keep scroll bars aligned in front
 * of the zoomable area.
 *
 * Note: in the current version, this is an undocumented capability.
 *       Class {@link RangeBar} uses it, but it is experimental.
 *       It may change in a future version.
 */
public void propertyChange(final PropertyChangeEvent event) {
}

/**
 * Synchronise la position et l'étendu des models <var>x</var> et
 * <var>y</var> avec la position du zoom. Les models <var>x</var>
 * et <var>y</var> sont généralement associés à des barres de defilements
 * horizontale et verticale. Lorsque la position d'une barre de défilement
 * est ajustée, le zomm sera ajusté en conséquence. Inversement, lorsque le
 * zoom est modifié, les positions et étendus des barres de défilements sont
 * ajustées en conséquence.
 *
 * @param x Modèle de la barre de défilement horizontale,
 *          ou <code>null</code> s'il n'y en a pas.
 * @param y Modèle de la barre de défilement verticale,
 *          ou <code>null</code> s'il n'y en a pas.
 */
public void tieModels(final BoundedRangeModel x, final BoundedRangeModel y) {
    if (x!=null || y!=null) {
}

/**
 * Synchronises the position and the range of the models <var>x</var> and
 * <var>y</var> with the position of the zoom.  The models <var>x</var>
 * and <var>y</var> are generally associated with horizontal and vertical
 * scrollbars.  When the position of a scrollbar is adjusted, the zoom is
 * consequently adjusted. Inversely, when the zoom is modified, the
 * positions and ranges of the scrollbars are consequently adjusted.
 *
 * @param x Model of the horizontal scrollbar or <code>null</code> if there
 *          isn't one.
 * @param y Model of the vertical scrollbar or <code>null</code> if there
 *          isn't one.
 */
public void tieModels(final BoundedRangeModel x, final BoundedRangeModel y) {
    if (x!=null || y!=null) {
}

/**
 * Annule la synchronisation entre les models <var>x</var> et <var>y</var>
 * spécifiés et le zoom de cet objet <code>ZoomPane</code>. Les objets
 * {@link ChangeListener} et {@link ZoomChangeListener} qui avait été créés
 * seront supprimés.
 *
 * @param x Modèle de la barre de défilement horizontale,
 *          ou <code>null</code> s'il n'y en a pas.
 * @param y Modèle de la barre de défilement verticale,
 *          ou <code>null</code> s'il n'y en a pas.
 */
public void untieModels(final BoundedRangeModel x, final BoundedRangeModel y) {
    final EventListener[] listeners=getListeners(ZoomChangeListener.class);
}

/**
 * Cancels the synchronisation between the specified <var>x</var> and
 * <var>y</var> models and the zoom of this <code>ZoomPane</code> object.
 * The {@link ChangeListener} and {@link ZoomChangeListener} objects that
 * were created are deleted.
 *
 * @param x Model of the horizontal scrollbar or <code>null</code> if there
 *          isn't one.
 * @param y Model of the vertical scrollbar or <code>null</code> if there
 *          isn't one.
 */
public void untieModels(final BoundedRangeModel x, final BoundedRangeModel y) {
    final EventListener[] listeners=getListeners(ZoomChangeListener.class);
}

/**
 * Objet ayant la charge de synchronizer un objet {@link JScrollPane}
 * avec des barres de défilements. Bien que ce ne soit généralement pas
 * utile, il serait possible de synchroniser plusieurs paires d'objets
 * {@link BoundedRangeModel} sur un  même objet <code>ZoomPane</code>.
 *
 * @author Martin Desruisseaux
 * @version 1.0
}

/**
 * Object responsible for synchronizing a {@link JScrollPane} object with
 * scrollbars.  Whilst not generally useful, it would be possible to
 * synchronize several pairs of {@link BoundedRangeModel} objects on one
 * <code>ZoomPane</code> object.
 *
 * @author Martin Desruisseaux
 * @version 1.0
 */
private final class Synchronizer implements ChangeListener, ZoomChangeListener {
    /**
     * Modèle à synchroniser avec {@link ZoomPane}.
     */
    public final BoundedRangeModel xm,ym;

    /**
     * Indique si les barres de défilements sont en train
     * d'être ajustées en réponse à {@link #zoomChanged}.
     * Si c'est la cas, {@link #stateChanged} ne doit pas
     * faire d'autres ajustements.
     */
    private transient boolean isAdjusting;

    /**
     * Cached <code>ZoomPane</code> bounds. Used in order
     * to avoid two many object allocation on the heap.
     */
    private transient Rectangle bounds;

    /**
     * Construit un objet qui synchronisera une paire de
     * {@link BoundedRangeModel} avec {@link ZoomPane}.
     */
    public Synchronizer(final BoundedRangeModel xm, final BoundedRangeModel ym) {
        this.xm = xm;
 */
private final class Synchronizer implements ChangeListener, ZoomChangeListener {
    /**
     * Model to synchronize with {@link ZoomPane}.
     */
    public final BoundedRangeModel xm,ym;

    /**
     * Indicates whether the scrollbars are being adjusted in
     * response to {@link #zoomChanged}.
     * If this is the case, {@link #stateChanged} mustn't make any
     * other adjustments.
     */
    private transient boolean isAdjusting;

    /**
     * Cached <code>ZoomPane</code> bounds. Used in order
     * to avoid too many object allocations on the heap.
     */
    private transient Rectangle bounds;

    /**
     * Constructs an object which synchronises a pair of
     * {@link BoundedRangeModel} with {@link ZoomPane}.
     */
    public Synchronizer(final BoundedRangeModel xm, final BoundedRangeModel ym) {
        this.xm = xm;
}

/**
 * Méthode appelée automatiquement chaque fois que la
 * position d'une des barres de défilement a changée.
 */
public void stateChanged(final ChangeEvent event) {
    if (!isAdjusting) {
}

/**
 * Method called automatically each time the position of one of the
 * scrollbars changes.
 */
public void stateChanged(final ChangeEvent event) {
    if (!isAdjusting) {
final boolean valueIsAdjusting=((BoundedRangeModel) event.getSource()).getValueIsAdjusting();
if (paintingWhileAdjusting || !valueIsAdjusting) {
    /*
     * Scroll view coordinates are computed with the following
     * steps:
     *
     *   1) Get the logical coordinates for the whole area.
     *   2) Transform to pixel space using current zoom.
     *   3) Clip to the scroll bars position (in pixels).
     *   4) Transform back to the logical space.
     *   5) Set the visible area to the resulting rectangle.
     */
final boolean valueIsAdjusting=((BoundedRangeModel) event.getSource()).getValueIsAdjusting();
if (paintingWhileAdjusting || !valueIsAdjusting) {
    /*
     * Scroll view coordinates are computed using the
     * following steps:
     *
     *   1) Get the logical coordinates for the whole area.
     *   2) Transform to pixel space using current zoom.
     *   3) Clip to the scrollbar's position (in pixels).
     *   4) Transform back to the logical space.
     *   5) Set the visible area to the resulting rectangle.
     */
}

/**
 * Méthode appelée chaque fois que le zoom a changé.
 *
 * @param change Ignoré. Peut être nul, et sera
 *               effectivement parfois nul.
 */
public void zoomChanged(final ZoomChangeEvent change) {
    if (!isAdjusting) {
}

/**
 * Method called each time the zoom changes.
 *
 * @param change Ignored. Can be null and will effectively sometimes
 *               be null.
 */
public void zoomChanged(final ZoomChangeEvent change) {
    if (!isAdjusting) {
}

/**
 * Procède à l'ajustement des valeurs d'un model. Les minimums et maximums
 * seront ajustés au besoin afin d'inclure la valeur et son étendu. Cet
 * ajustement est nécessaire pour éviter un comportement chaotique lorsque
 * l'utilisateur fait glisser l'ascensceur pendant qu'une partie du
 * graphique est en dehors de la zone qui était initialement prévue par
 * {@link #getArea}.
 */
private static void setRangeProperties(final BoundedRangeModel model,
                                       final double value, final int extent,
}

/**
 * Adjusts the values of a model.  The minimums and maximums are adjusted
 * as needed in order to include the value and its range. This adjustment
 * is necessary in order to avoid chaotic behaviour when the user
 * drags the slider whilst a part of the graphic is outside the zone
 * initially planned for {@link #getArea}.
 */
private static void setRangeProperties(final BoundedRangeModel model,
                                       final double value, final int extent,
}

/**
 * Modifie la position en pixels de la partie visible de
 * <code>ZoomPanel</code>. Soit <code>viewSize</code> les dimensions en
 * pixels qu'aurait <code>ZoomPane</code> si sa surface visible couvrait
 * la totalité de la région {@link #getArea} avec le zoom courant (Note:
 * cette dimension <code>viewSize</code> peut être obtenues par {@link
 * #getPreferredSize} si {@link #setPreferredSize} n'a pas été appelée avec
 * une valeur non-nulle). Alors par définition la région {@link #getArea}
 * convertit dans l'espace des pixels donnerait le rectangle
 *
 * <code>bounds=Rectangle(0,&nbsp;0,&nbsp;,viewSize.width,&nbsp;,viewSize.height)</code>.
 *
 * Cette méthode <code>scrollRectToVisible</code> permet de définir la
 * sous-région de <code>bounds</code> qui doit apparaître dans la fenêtre
 * <code>ZoomPane</code>.
 */
public void scrollRectToVisible(final Rectangle rect) {
    Rectangle2D area=getArea();
    if (isValid(area)) {
        area=XAffineTransform.transform(zoom, area, null);
        area.setRect(area.getX()+rect.getX(), area.getY()+rect.getY(), rect.getWidth(), rect.getHeight());
        try {
            setVisibleArea(XAffineTransform.inverseTransform(zoom, area, area));
        } catch (NoninvertibleTransformException exception) {
}

/**
 * Modifies the position in pixels of the visible part of
 * <code>ZoomPane</code>. <code>viewSize</code> is the size
 * <code>ZoomPane</code> would be (in pixels) if its visible surface
 * covered the whole of the {@link #getArea} region with the current
 * zoom (Note: <code>viewSize</code> can be obtained by {@link
 * #getPreferredSize} if {@link #setPreferredSize} hasn't been called
 * with a non-null value). Therefore, by definition, the region
 * {@link #getArea} converted into pixel space would give the rectangle
 * <code>bounds=Rectangle(0,&nbsp;0,&nbsp;,viewSize.width,&nbsp;,viewSize.height)</code>.
 *
 * This <code>scrollRectToVisible</code> method allows us to define the
 * sub-region of <code>bounds</code> which must appear in the
 * <code>ZoomPane</code> window.
 */
public void scrollRectToVisible(final Rectangle rect) {
    Rectangle2D area=getArea();
    if (isValid(area)) {
        area=XAffineTransform.transform(zoom, area, null);
        area.setRect(area.getX() + rect.getX(), area.getY() + rect.getY(),
                     rect.getWidth(), rect.getHeight());
        try {
            setVisibleArea(XAffineTransform.inverseTransform(zoom, area, area));
        } catch (NoninvertibleTransformException exception) {
}

/**
 * Indique si cet objet <code>ZoomPane</code> doit être redessiné pendant
 * que l'utilisateur déplace le glissoir des barres de défilements. Les
 * barres de défilements (ou autres models) concernées sont celles qui ont
 * été synchronisées avec cet objet <code>ZoomPane</code> à l'aide de la
 * méthode {@link #tieModels}. La valeur par défaut est <code>false</code>,
 * ce qui signifie que <code>ZoomPane</code> attendra que l'utilisateur ait
 * relaché le glissoir avant de se redessiner.
 */
public boolean isPaintingWhileAdjusting() {
    return paintingWhileAdjusting;
}

/**
 * Indicates whether or not this <code>ZoomPane</code> object should be
 * repainted when the user moves the scrollbar slider. The scrollbars (or
 * other models) involved are those which have been synchronised with
 * this <code>ZoomPane</code> object through the {@link #tieModels} method.
 * The default value is <code>false</code>, which means that
 * <code>ZoomPane</code> will wait until the user releases the slider
 * before repainting.
 */
public boolean isPaintingWhileAdjusting() {
    return paintingWhileAdjusting;
}

/**
 * Définit si cet objet <code>ZoomPane</code> devra redessiner la carte
 * pendant que l'utilisateur déplace le glissoir des barres de défilements.
 * Il vaut mieux avoir un ordinateur assez rapide pour donner la valeur
 * <code>true</code> à ce drapeau.
 */
public void setPaintingWhileAdjusting(final boolean flag) {
    paintingWhileAdjusting = flag;
}

/**
 * Defines whether or not this <code>ZoomPane</code> object should repaint
 * the map when the user moves the scrollbar slider.
 * A fast computer is recommended if this flag is to be set to
 * <code>true</code>.
 */
public void setPaintingWhileAdjusting(final boolean flag) {
    paintingWhileAdjusting = flag;
}

/**
 * Déclare qu'une partie de ce paneau a besoin d'être redéssinée. Cette
 * méthode ne fait que redéfinir la méthode de la classe parente pour tenir
 * compte du cas où la loupe serait affichée.
 */
public void repaint(final long tm, final int x, final int y,
                    final int width, final int height) {
    super.repaint(tm, x, y, width, height);
    if (magnifier!=null && magnifier.intersects(x,y,width,height)) {
        // Si la partie à dessiner est à l'intérieur de la loupe,
        // le fait que la loupe fasse un agrandissement nous oblige
        // à redessiner un peu plus que ce qui avait été demandé.
        repaintMagnifier();
    }
}

/**
 * Déclare que la loupe a besoin d'être redéssinée. Une commande
 * {@link #repaint()} sera envoyée avec comme coordonnées les limites
 * de la loupe (en tenant compte de sa bordure).
 */
private void repaintMagnifier() {
    final Rectangle bounds=magnifier.getBounds();
}

/**
 * Declares that a part of this pane needs to be repainted. This method
 * simply redefines the method of the parent class in order to take into
 * account a case where the magnifying glass is displayed.
 */
public void repaint(final long tm, final int x, final int y,
                    final int width, final int height) {
    super.repaint(tm, x, y, width, height);
    if (magnifier!=null && magnifier.intersects(x,y,width,height)) {
        // If the part to paint is inside the magnifying glass,
        // the fact that the magnifying glass is zooming in means
        // we have to repaint a little more than that which was requested.
        repaintMagnifier();
    }
}

/**
 * Declares that the magnifying glass needs to be repainted. A
 * {@link #repaint()} command is sent with the bounds of the
 * magnifying glass as coordinates (taking into account its
 * outline).
 */
private void repaintMagnifier() {
    final Rectangle bounds=magnifier.getBounds();
}

/**
 * Paints the magnifier. This method is invoked after
 * {@link #paintComponent(Graphics2D)} if a magnifier
 * is visible.
 */
protected void paintMagnifier(final Graphics2D graphics) {
}

/**
 * Paints the magnifying glass. This method is invoked after
 * {@link #paintComponent(Graphics2D)} if a magnifying glass
 * is visible.
 */
protected void paintMagnifier(final Graphics2D graphics) {
graphics.setColor (magnifierBorder);
graphics.draw     (magnifier);
graphics.setStroke(stroke);
graphics.clip     (magnifier); // Coordonnées en pixels!
graphics.setColor (magnifierColor);
graphics.fill     (magnifier.getBounds2D());
graphics.setPaint (paint);
graphics.setColor (magnifierBorder);
graphics.draw     (magnifier);
graphics.setStroke(stroke);
graphics.clip     (magnifier); // Coordinates in pixels!
graphics.setColor (magnifierColor);
graphics.fill     (magnifier.getBounds2D());
graphics.setPaint (paint);
    graphics.translate(+centerX, +centerY);
    graphics.scale    (magnifierPower, magnifierPower);
    graphics.translate(-centerX, -centerY);
    // Note: les transformations effectuées ici doivent être identiques
    //       à celles qui sont faites dans {@link #pixelToLogical}.
    paintComponent    (graphics);
}

/**
 * Paints this component. Subclass must override this method in order to
 * drawn the <code>ZoomPane</code> content. For must implementations, the
 * first line in this method will be
 *
 * <code>graphics.transform({@link #zoom})</code>.
    graphics.translate(+centerX, +centerY);
    graphics.scale    (magnifierPower, magnifierPower);
    graphics.translate(-centerX, -centerY);
    // Note: the transformations performed here must be identical to those
    //       performed in {@link #pixelToLogical}.
    paintComponent    (graphics);
}

/**
 * Paints this component. Subclass must override this method in order to
 * draw the <code>ZoomPane</code> content. For most implementations, the
 * first line in this method will be
 *
 * <code>graphics.transform({@link #zoom})</code>.
/**
 * Paints this component. This method is declared <code>final</code>
 * in order to avoir unintentional overriding. Override
 * {@link #paintComponent(Graphics2D)} instead.
 */
protected final void paintComponent(final Graphics graphics) {
/**
 * Paints this component. This method is declared <code>final</code>
 * in order to avoid unintentional overriding. Override
 * {@link #paintComponent(Graphics2D)} instead.
 */
protected final void paintComponent(final Graphics graphics) {
flag=IS_PAINTING;
super.paintComponent(graphics);
/*
 * La méthode <code>JComponent.paintComponent(...)</code> crée un objet <code>Graphics2D</code>
 * temporaire, puis appelle <code>ComponentUI.update(...)</code> avec en paramètre ce graphique.
 * Cette méthode efface le fond de l'écran, puis appelle <code>ComponentUI.paint(...)</code>.
 * Or, cette dernière a été redéfinie plus haut (notre objet {@link #UI}) de sorte qu'elle
 * appelle elle-même {@link #paintComponent(Graphics2D)}. Un chemin compliqué, mais on a pas
 * tellement le choix et c'est somme toute assez efficace.
 */
if (magnifier!=null) {
    flag=IS_PAINTING_MAGNIFIER;
flag=IS_PAINTING;
super.paintComponent(graphics);
/*
 * The <code>JComponent.paintComponent(...)</code> method creates a
 * temporary <code>Graphics2D</code> object, then calls
 * <code>ComponentUI.update(...)</code> with this graphic as a
 * parameter.  This method clears the screen background then calls
 * <code>ComponentUI.paint(...)</code>.
 * This last method has been redefined further up (our {@link #UI})
 * object in such a way that it calls itself
 * {@link #paintComponent(Graphics2D)}. A complicated path, but we
 * don't have much choice and it is, after all, quite efficient.
 */
if (magnifier!=null) {
    flag=IS_PAINTING_MAGNIFIER;
/**
 * Prints this component. This method is declared <code>final</code>
 * in order to avoir unintentional overriding. Override
 * {@link #printComponent(Graphics2D)} instead.
 */
protected final void printComponent(final Graphics graphics) {
/**
 * Prints this component. This method is declared <code>final</code>
 * in order to avoid unintentional overriding. Override
 * {@link #printComponent(Graphics2D)} instead.
 */
protected final void printComponent(final Graphics graphics) {
}

/**
 * Retourne la dimension (en pixels) qu'aurait <code>ZoomPane</code> s'il
 * affichait la totalité de la région {@link #getArea} avec le zoom courant
 * ({@link #zoom}). Cette méthode est pratique pour déterminer les valeurs
 * maximales à affecter aux barres de défilement. Par exemple la barre
 * horizontale pourrait couvrir la plage <code>[0..viewSize.width]</code>
 * tandis que la barre verticale pourrait couvrir la plage
 * <code>[0..viewSize.height]</code>.
 */
private final Dimension getViewSize() {
}

/**
 * Returns the size (in pixels) that <code>ZoomPane</code> would have if
 * it displayed the whole of the {@link #getArea} region with the current
 * zoom ({@link #zoom}). This method is practical for determining the
 * maximum values to assign to the scrollbars. For example, the horizontal
 * bar could cover the range <code>[0..viewSize.width]</code>
 * whilst the vertical bar could cover the range
 * <code>[0..viewSize.height]</code>.
 */
private final Dimension getViewSize() {
}

/**
 * Retourne les marges de cette composante. Cette méthode fonctionne comme
 * <code>super.getInsets(insets)</code>, mais accepte un argument nul. Cette
 * méthode peut être redéfinie si on veut effectuer les zooms sur une
 * portion du graphique plutôt que sur l'ensemble.
 */
public Insets getInsets(final Insets insets) {
    return super.getInsets((insets!=null) ? insets : new Insets(0,0,0,0));
}

/**
 * Returns the Insets of this component. This method works like
 * <code>super.getInsets(insets)</code>, but accepts a null argument. This
 * method can be redefined if it is necessary to perform zooms on a
 * part of the graphic rather than the whole thing.
 */
public Insets getInsets(final Insets insets) {
    return super.getInsets((insets!=null) ? insets : new Insets(0,0,0,0));
}

/**
 * Retourne les marges de cette composante. Cette méthode est déclarée final
 * afin d'éviter toute confusion. Si vous voulez retourner d'autres marges,
 * il faut redéfinir {@link #getInsets(Insets)}.
 */
public final Insets getInsets() {
    return getInsets(null);
}

/**
 * Returns the Insets of this component.  This method is declared final in
 * order to avoid confusion. If you want to return other Insets you must
 * redefine {@link #getInsets(Insets)}.
 */
public final Insets getInsets() {
    return getInsets(null);
}

/**
 * Informe <code>ZoomPane</code> que l'interface GUI a changé.
 * L'utilisateur n'a pas à appeler cette méthode directement.
 */
public void updateUI() {
    navigationPopupMenu=null;
}

/**
 * Informs <code>ZoomPane</code> that the GUI has changed. The user
 * doesn't have to call this method directly.
 */
public void updateUI() {
    navigationPopupMenu=null;
/**
 * Invoked when an affine transform that should be invertible is not.
 * Default implementation log the stack trace and reset the zoom.
 *
 * @param methodName The caller's method name.
 * @param exception  The exception.
/**
 * Invoked when an affine transform that should be invertible is not.
 * Default implementation logs the stack trace and resets the zoom.
 *
 * @param methodName The caller's method name.
 * @param exception  The exception.
}

/**
 * Invoked when an unexpected exception occured.
 * Default implementation log the stack trace.
 *
 * @param methodName The caller's method name.
 * @param exception  The exception.
}

/**
 * Invoked when an unexpected exception occurs.
 * Default implementation logs the stack trace.
 *
 * @param methodName The caller's method name.
 * @param exception  The exception.
 * <code>ZoomPane</code> class. This method is invoked
 * from {@link #setPreferredArea} and {@link #setVisibleArea}.
 *
 * @param methodName The caller's method name (e.g. <code>"setArea"</code>).
 * @param       area The coordinates to log (may be <code>null</code>).
 */
private static void log(final String methodName, final Rectangle2D area) {
 * <code>ZoomPane</code> class. This method is invoked
 * from {@link #setPreferredArea} and {@link #setVisibleArea}.
 *
 * @param methodName The caller's method name
 *                  (e.g. <code>"setArea"</code>).
 * @param       area The coordinates to log (may be <code>null</code>).
 */
private static void log(final String methodName, final Rectangle2D area) {
* Events are logged in the <code>"org.geotools.gui"</code> logger
* with {@link Level#FINE}. <code>ZoomPane</code> invokes this method
* for logging any [@link #setPreferredArea} and {@link #setVisibleArea}
* invocations. Subclasses may invokes this method for logging some other
* kinds of area changes.
*
* @param  className The fully qualified caller's class name
* Events are logged in the <code>"org.geotools.gui"</code> logger
* with {@link Level#FINE}. <code>ZoomPane</code> invokes this method
* for logging any [@link #setPreferredArea} and {@link #setVisibleArea}
* invocations. Subclasses may invoke this method for logging some other
* kinds of area changes.
*
* @param  className The fully qualified caller's class name
}

/**
 * Vérifie si le rectangle <code>rect</code> est valide. Le rectangle sera
 * considéré invalide si sa largeur ou sa hauteur est inférieure ou égale à
 * 0, ou si une de ses coordonnées est infinie ou NaN.
 */
private static boolean isValid(final Rectangle2D rect) {
    if (rect==null) {
}

/**
 * Checks whether the rectangle <code>rect</code> is valid.  The rectangle
 * is considered invalid if its length or width is less than or equal to 0,
 * or if one of its coordinates is infinite or NaN.
 */
private static boolean isValid(final Rectangle2D rect) {
    if (rect==null) {