gt:         return (value < min) ? min : (value > max) ? max : value;
context: /*
   * Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de)
   *
   * This program is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License as published by the Free
   * Software Foundation; either version 3 of the License, or (at your option)
   * any later version.
   * This program 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 for
   * more details.
   *
   * You should have received a copy of the GNU General Public License along
   * with this program; if not, see http://www.gnu.org/licenses/
   */
  package com.bc.ceres.glayer.swing;
  
  import com.bc.ceres.core.Assert;
  import com.bc.ceres.grender.AdjustableView;
  import com.bc.ceres.grender.Viewport;
  import com.bc.ceres.grender.ViewportListener;
  
  import javax.swing.JComponent;
  import javax.swing.JPanel;
  import javax.swing.JScrollBar;
  import javax.swing.event.ChangeEvent;
  import javax.swing.event.ChangeListener;
  import java.awt.Component;
  import java.awt.Dimension;
  import java.awt.Insets;
  import java.awt.Rectangle;
  import java.awt.event.ComponentAdapter;
  import java.awt.event.ComponentEvent;
  import java.awt.geom.Rectangle2D;
  
  
  /**
   * A <code>ViewPane</code> is an alternative to {@link javax.swing.JScrollPane}
   * when you need to scroll an infinite area given in floating-point coordinates.
   * <p/>
   * In opposite to {@link javax.swing.JScrollPane}, we don't scroll a view  given
   * by a {@link javax.swing.JComponent} but it's {@link Viewport}. For this reason
   * the view component must implement the {@link AdjustableView} interface.
   *
   * @author Norman Fomferra
   * @version $Revision$ $Date$
   */
  public class AdjustableViewScrollPane extends JPanel {
      private static final long serialVersionUID = -2634482999458990218L;
  
      // Extension of model bounds in view coordinates
      private static final int MODEL_BOUNDS_EXTENSION = 10; // pixels
      // Maximum scroll bar value
      private static final int MAX_SB_VALUE = 10000;
  
      private JScrollBar horizontalScrollBar;
      private JScrollBar verticalScrollBar;
      private JComponent cornerComponent;
      private JComponent viewComponent;
      private AdjustableView adjustableView;
      private Rectangle2D scrollArea;
      private boolean updatingScrollBars;
      private boolean scrollBarsUpdated;
      private ViewportChangeHandler viewportChangeHandler;
      private boolean hsbVisible;
      private boolean vsbVisible;
  
      private boolean debug = false;
  
      /**
       * Constructs a new view pane with an empty view component.
       */
      public AdjustableViewScrollPane() {
          this(null);
      }
  
      /**
       * Constructs a new view pane with the given view viewComponent
       *
       * @param viewComponent the view viewComponent. If not null, it must implement {@link AdjustableView}.
       */
      public AdjustableViewScrollPane(JComponent viewComponent) {
          super(null);
          Assert.notNull(viewComponent, "viewComponent");
          Assert.argument(viewComponent instanceof AdjustableView, "viewComponent");
          scrollArea = new Rectangle2D.Double();
          viewportChangeHandler = new ViewportChangeHandler();
          setViewComponent(viewComponent);
          setCornerComponent(createCornerComponent());
          final ChangeListener scrollBarCH = new ScrollBarChangeHandler();
          horizontalScrollBar = createHorizontalScrollbar();
          horizontalScrollBar.getModel().addChangeListener(scrollBarCH);
          verticalScrollBar = createVerticalScrollBar();
          verticalScrollBar.getModel().addChangeListener(scrollBarCH);
          addComponentListener(new ResizeHandler());
      }
  
      public AdjustableView getAdjustableView() {
          return (AdjustableView) viewComponent;
      }
  
      public JComponent getViewComponent() {
          return viewComponent;
      }
  
      /**
       * Constructs a new view pane with the given view which must implement the {@link AdjustableView} interface.
       *
       * @param viewComponent a view component  implement {@link AdjustableView}.
       */
      public void setViewComponent(JComponent viewComponent) {
          if (this.viewComponent != viewComponent) {
              if (this.viewComponent != null) {
                  this.adjustableView.getViewport().removeListener(viewportChangeHandler);
                  remove(this.viewComponent);
              }
              this.viewComponent = viewComponent;
              this.adjustableView = null;
              if (viewComponent != null) {
                  this.adjustableView = (AdjustableView) viewComponent;
                  this.adjustableView.getViewport().addListener(viewportChangeHandler);
                  add(this.viewComponent);
              }
              revalidate();
              validate();
          }
      }
  
      public JComponent getCornerComponent() {
          return cornerComponent;
      }
  
      public void setCornerComponent(JComponent cornerComponent) {
          if (this.cornerComponent != cornerComponent) {
              this.cornerComponent = cornerComponent;
              revalidate();
              validate();
          }
      }
  
      @Override
      public void doLayout() {
          if (viewComponent == null || !viewComponent.isVisible()) {
              return;
          }
          if (!scrollBarsUpdated) {
              updateScrollBars();
              updateScrollBarIncrements();
          }
  
          final Insets insets = getInsets();
          final int width = getWidth() - (insets.left + insets.right);
          final int height = getHeight() - (insets.top + insets.bottom);
          if (width <= 0 || height <= 0) {
              return;
          }
  
          // x1              w1              x2 w2
          // +-------------------------------+---+ y1
          // |                               |   |
          // |                               | v |
          // |                               | s |
          // |                               | b |
          // |             view              |   | h1
          // |                               |   |
          // |                               |   |
          // |                               |   |
          // |                               |   |
          // +-------------------------------+---+ y2
          // |             hsb               |   | h2
          // +-------------------------------+---+
          //
  
          if (hsbVisible && vsbVisible) {
              final Dimension hsbSize = horizontalScrollBar.getPreferredSize();
              final Dimension vsbSize = verticalScrollBar.getPreferredSize();
              final int x1 = insets.left;
              final int y1 = insets.top;
              final int w2 = vsbSize.width;
              final int h2 = hsbSize.height;
              final int w1 = width - w2;
              final int h1 = height - h2;
              final int x2 = x1 + w1;
              final int y2 = y1 + h1;
              viewComponent.setBounds(x1, y1, w1, h1);
              verticalScrollBar.setBounds(x2, y1, w2, h1);
              horizontalScrollBar.setBounds(x1, y2, w1, h2);
              if (cornerComponent != null) {
                  cornerComponent.setBounds(x2, y2, w2, h2);
              }
          } else if (hsbVisible) {
              final Dimension hsbSize = horizontalScrollBar.getPreferredSize();
              final int x1 = insets.left;
              final int y1 = insets.top;
              final int w1 = width;
              final int h2 = hsbSize.height;
              final int h1 = height - h2;
              final int y2 = y1 + h1;
              viewComponent.setBounds(x1, y1, w1, h1);
              horizontalScrollBar.setBounds(x1, y2, w1, h2);
          } else if (vsbVisible) {
              final Dimension vsbSize = verticalScrollBar.getPreferredSize();
              final int x1 = insets.left;
              final int y1 = insets.top;
              final int w2 = vsbSize.width;
              final int w1 = width - w2;
              final int h1 = height;
              final int x2 = x1 + w1;
              viewComponent.setBounds(x1, y1, w1, h1);
              verticalScrollBar.setBounds(x2, y1, w2, h1);
          } else {
              final int x1 = insets.left;
              final int y1 = insets.top;
              viewComponent.setBounds(x1, y1, width, height);
          }
  
          viewComponent.doLayout();
      }
  
      @Override
      protected void addImpl(Component comp, Object constraints, int index) {
          if (comp != horizontalScrollBar &&
                  comp != verticalScrollBar &&
                  comp != cornerComponent &&
                  comp != viewComponent) {
              throw new IllegalArgumentException();
          }
          super.addImpl(comp, constraints, index);
      }
  
      /**
       * @return <code>new JScrollBar(JScrollBar.HORIZONTAL)</code>
       */
      protected JScrollBar createHorizontalScrollbar() {
          return new JScrollBar(JScrollBar.HORIZONTAL);
      }
  
      /**
       * @return <code>new JScrollBar(JScrollBar.VERTICAL)</code>
       */
      protected JScrollBar createVerticalScrollBar() {
          return new JScrollBar(JScrollBar.VERTICAL);
      }
  
      /**
       * @return <code>new JPanel()</code>
       */
      protected JPanel createCornerComponent() {
          return new JPanel();
      }
  
  
      private void updateViewport() {
          if (updatingScrollBars || adjustableView == null) {
              if (debug) {
                  System.out.println("AdjustableViewScrollPane.updateViewport: return!");
              }
              return;
          }
  
          final Rectangle va = getViewBounds();
          double vx = va.getX();
          double vy = va.getY();
  
          final Rectangle2D sa = scrollArea;
          if (hsbVisible) {
              final int hsbValue = horizontalScrollBar.getValue();
              vx = sa.getX() + hsbValue * sa.getWidth() / MAX_SB_VALUE;
          }
          if (vsbVisible) {
              final int vsbValue = verticalScrollBar.getValue();
              vy = sa.getY() + vsbValue * sa.getHeight() / MAX_SB_VALUE;
          }
          if (hsbVisible || vsbVisible) {
              if (debug) {
                  System.out.println("AdjustableViewScrollPane.updateViewport:");
                  System.out.println("  vx = " + vx);
                  System.out.println("  vy = " + vy);
                  System.out.println("");
              }
              adjustableView.getViewport().moveViewDelta(-vx, -vy);
          }
      }
  
      private void updateScrollBars() {
          if (adjustableView == null) {
              if (debug) {
                  System.out.println("AdjustableViewScrollPane.updateScrollBars: return!");
              }
              return;
          }
  
          // View bounds in view coordinates
          final Rectangle2D va = getViewBounds();
          if (va.isEmpty()) {
              remove(horizontalScrollBar);
              remove(verticalScrollBar);
              if (cornerComponent != null) {
                  remove(cornerComponent);
              }
              return;
          }
  
          // Model bounds in view coordinates
          final Rectangle2D ma = adjustableView.getViewport().getModelToViewTransform().createTransformedShape(adjustableView.getMaxVisibleModelBounds()).getBounds2D();
          // Following code make it easier to scroll out of the model area
          ma.add(ma.getX() - MODEL_BOUNDS_EXTENSION, ma.getY() - MODEL_BOUNDS_EXTENSION);
          ma.add(ma.getX() + ma.getWidth() + MODEL_BOUNDS_EXTENSION, ma.getY() + ma.getHeight() + MODEL_BOUNDS_EXTENSION);
  
          // Scroll bounds in view coordinates
          final Rectangle2D sa = ma.createUnion(va);
  
          //  x1,x2,y1,y2(+)   no scrollbars         x1,x2,y1,y2(-)  V+H-scrollbars
          //  +--------------------------------+     +--------------------------------+
          //  | va             ^               |     | ma             ^               |
          //  |                |               |     |                |               |
          //  |                y1              |     |                y1              |
          //  |                |               |     |                |               |
          //  |                v               |     |                v               |
          //  |        +--------------+        |     |        +--------------+        |
          //  |        | ma           |        |     |        | va           V        |
          //  |<--x1-->|              |<--x2-->|     |<--x1-->|              V<--x2-->|
          //  |        |              |        |     |        |              V        |
          //  |        +--------------+        |     |        +HHHHHHHHHHHHHH+        |
          //  |                ^               |     |                ^               |
          //  |                |               |     |                |               |
          //  |                y2              |     |                y2              |
          //  |                |               |     |                |               |
          //  |                v               |     |                v               |
          //  +--------------------------------+     +--------------------------------+
          //
          //  x1,y1,y2(+) x2(-)  H-scrollbar                 x1,y1,y2(+) x2(-)  H,V-scrollbars
          //  +--------------------------------+             +--------------------------------+
          //  | va                     ^       |             | ma                     ^       |
          //  |                        |       |             |                        |       |
          //  |                        y1      |             |                        y1      |
          //  |                        |       |<--x2-->     |                        |       |<--x2-->
          //  |                        v       |             |                        v       |
          //  |                     +-------------------+    |                     +-------------------+
          //  |                     | ma                |    |                     | va                V
          //  |<---------x1-------->|                   |    |<---------x1-------->|                   V
          //  |                     |                   |    |                     |                   V
          //  |                     +-------------------+    |                     +HHHHHHHHHHHHHHHHHHH+
          //  |                        ^       |             |                        ^       |
          //  |                        |       |             |                        |       |
          //  |                        y2      |             |                        y2      |
          //  |                        |       |             |                        |       |
          //  |                        v       |             |                        v       |
          //  +HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH+             +--------------------------------+
          //
          final double dx1 = ma.getX() - va.getX();
          final double dy1 = ma.getY() - va.getY();
          final double dx2 = (va.getX() + va.getWidth()) - (ma.getX() + ma.getWidth());
          final double dy2 = (va.getY() + va.getHeight()) - (ma.getY() + ma.getHeight());
  
          boolean hsbVisible = dx1 < 0 || dx2 < 0;
          boolean vsbVisible = dy1 < 0 || dy2 < 0;
  
          if (this.hsbVisible != hsbVisible || this.vsbVisible != vsbVisible) {
              if (this.hsbVisible != hsbVisible) {
                  if (hsbVisible) {
                      add(horizontalScrollBar);
                  } else {
                      remove(horizontalScrollBar);
                  }
              }
              if (this.vsbVisible != vsbVisible) {
                  if (vsbVisible) {
                      add(verticalScrollBar);
                  } else {
                      remove(verticalScrollBar);
                  }
              }
              if (cornerComponent != null) {
                  if (hsbVisible && vsbVisible) {
                      add(cornerComponent);
                  } else {
                      remove(cornerComponent);
                  }
              }
              this.hsbVisible = hsbVisible;
              this.vsbVisible = vsbVisible;
          }
  
          if (debug) {
              System.out.println("AdjustableViewScrollPane.updateScrollBars:");
              System.out.println("  hsbVisible = " + vsbVisible);
              System.out.println("  hsbVisible = " + hsbVisible);
              System.out.println("  va = " + va);
              System.out.println("  ma = " + ma);
              System.out.println("  sa = " + sa);
              System.out.println("  dx1 = " + dx1 + ", dx2 = " + dx2);
              System.out.println("  dy1 = " + dy1 + ", dy2 = " + dy2);
              System.out.println();
          }
  
          scrollArea.setRect(sa);
  
          updatingScrollBars = true;
  
          if (hsbVisible) {
              int hsbValue = (int) Math.round(MAX_SB_VALUE * (va.getX() - sa.getX()) / sa.getWidth());
              hsbValue = clamp(hsbValue, 0, MAX_SB_VALUE);
              int hsbExtend = (int) Math.round(MAX_SB_VALUE * va.getWidth() / sa.getWidth());
              hsbExtend = clamp(hsbExtend, 0, MAX_SB_VALUE);
              horizontalScrollBar.setValues(hsbValue, hsbExtend, 0, MAX_SB_VALUE);
          }
  
          if (vsbVisible) {
              int vsbValue = (int) Math.round(MAX_SB_VALUE * (va.getY() - sa.getY()) / sa.getHeight());
              vsbValue = clamp(vsbValue, 0, MAX_SB_VALUE);
              int vsbExtend = (int) Math.round(MAX_SB_VALUE * va.getHeight() / sa.getHeight());
              vsbExtend = clamp(vsbExtend, 0, MAX_SB_VALUE);
              verticalScrollBar.setValues(vsbValue, vsbExtend, 0, MAX_SB_VALUE);
          }
  
          updatingScrollBars = false;
          scrollBarsUpdated = true;
      }
  
      private void updateScrollBarIncrements() {
          // we could set more reasonable increments at this place, e.g. using the viewport.modelArea property
          horizontalScrollBar.setUnitIncrement(Math.max(10, MAX_SB_VALUE / 50));
          horizontalScrollBar.setBlockIncrement(Math.max(10, MAX_SB_VALUE / 5));
          verticalScrollBar.setUnitIncrement(Math.max(10, MAX_SB_VALUE / 50));
          verticalScrollBar.setBlockIncrement(Math.max(10, MAX_SB_VALUE / 5));
      }
  
      private Rectangle getViewBounds() {
          return new Rectangle(0, 0, viewComponent.getWidth(), viewComponent.getHeight());
      }
  
      private static int clamp(int value, int min, int max) {
  