/**
   * Gets the scale factor that allows us to translate pixel values to scroll bar values and vice
   * versa. This assumes that the range of pixels spans from the last pixel of
   * <tt>scrollUpButton</tt> to the first pixel of <tt>scrollDownButton</tt> and excludes the pixels
   * taken up by <tt>handle</tt>.
   *
   * <p>To map from scroll bar values (<i>real values</i>) to pixel values, you multiply by the
   * value scale. To map from pixel values back to real values, you divide by the value scale.
   *
   * @return <tt>&lt;number of legal pixel values&gt; / &lt;number of legal real values&gt;</tt>
   */
  private float getValueScale() {
    ScrollBar scrollBar = (ScrollBar) getComponent();

    float valueScale;

    int start = scrollBar.getStart();
    int end = scrollBar.getEnd();
    int extent = scrollBar.getExtent();
    int maxLegalRealValue = end - extent;

    int numLegalRealValues = maxLegalRealValue - start + 1;
    int numLegalPixelValues;

    if (scrollBar.getOrientation() == Orientation.HORIZONTAL) {
      int availableWidth = getWidth() - scrollUpButton.getWidth() - scrollDownButton.getWidth() + 2;
      numLegalPixelValues = availableWidth - handle.getWidth() + 1;
    } else {
      int availableHeight =
          getHeight() - scrollUpButton.getHeight() - scrollDownButton.getHeight() + 2;
      numLegalPixelValues = availableHeight - handle.getHeight() + 1;
    }

    // the number of segments is one less than the number of values
    valueScale = numLegalPixelValues / ((float) numLegalRealValues - 1);

    return valueScale;
  }
    @Override
    public void paint(Graphics2D graphics) {
      // Apply scroll bar styles to the button
      ScrollButton scrollButton = (ScrollButton) getComponent();
      ScrollBar scrollBar = (ScrollBar) TerraScrollBarSkin.this.getComponent();
      Orientation orientation = scrollBar.getOrientation();

      int width = getWidth();
      int height = getHeight();

      Color backgroundColor;
      if (scrollButton.isEnabled()) {
        if (pressed) {
          backgroundColor = scrollButtonPressedBackgroundColor;
        } else if (highlighted) {
          backgroundColor = scrollButtonHighlightedBackgroundColor;
        } else {
          backgroundColor = scrollButtonBackgroundColor;
        }
      } else {
        backgroundColor = scrollButtonDisabledBackgroundColor;
      }

      Color brightBackgroundColor = TerraTheme.brighten(backgroundColor);

      // Paint the background
      Color gradientStartColor = pressed ? backgroundColor : brightBackgroundColor;
      Color gradientEndColor = pressed ? brightBackgroundColor : backgroundColor;

      if (orientation == Orientation.HORIZONTAL) {
        graphics.setPaint(
            new GradientPaint(0, 1, gradientStartColor, 0, height - 2, gradientEndColor));
      } else {
        graphics.setPaint(
            new GradientPaint(1, 0, gradientStartColor, width - 2, 0, gradientEndColor));
      }

      graphics.fillRect(1, 1, width - 2, height - 2);

      // Paint the border
      graphics.setPaint(borderColor);
      GraphicsUtilities.drawRect(graphics, 0, 0, width, height);

      // Determine the button image size
      ScrollButtonImage buttonImage = scrollButton.getButtonImage();
      int buttonImageWidth = buttonImage.getWidth();
      int buttonImageHeight = buttonImage.getHeight();

      // Paint the image
      Graphics2D imageGraphics = (Graphics2D) graphics.create();
      int buttonImageX = (width - buttonImageWidth) / 2;
      int buttonImageY = (height - buttonImageHeight) / 2;
      imageGraphics.translate(buttonImageX, buttonImageY);
      imageGraphics.clipRect(0, 0, buttonImageWidth, buttonImageHeight);
      buttonImage.paint(imageGraphics);
      imageGraphics.dispose();
    }
  /**
   * Propagates the scroll bar's enabled state to the scroll buttons.
   *
   * @param component The scroll bar.
   */
  @Override
  public void enabledChanged(Component component) {
    boolean enabled = component.isEnabled();

    scrollUpButton.setEnabled(enabled);
    scrollDownButton.setEnabled(enabled);

    invalidateComponent();
  }
  /**
   * Initiates automatic block scrolling. This only happens if the handle is visible since whether
   * the user clicked before or after the handle determines the direction of the scrolling.
   *
   * @param component The scroll bar.
   * @param button The button that was pressed.
   * @param x The x-coordinate of the event in the scroll bar's coordinate space.
   * @param y The y-coordinate of the event in the scroll bar's coordinate space.
   */
  @Override
  public boolean mouseDown(Component component, Mouse.Button button, int x, int y) {
    boolean consumed = super.mouseDown(component, button, x, y);

    if (button == Mouse.Button.LEFT && handle.isVisible()) {
      ScrollBar scrollBar = (ScrollBar) getComponent();

      // Begin automatic block scrolling. Calculate the direction of
      // the scroll by checking to see if the user pressed the mouse
      // in the area "before" the handle or "after" it.
      int direction;
      int realStopValue;

      if (scrollBar.getOrientation() == Orientation.HORIZONTAL) {
        direction = x < handle.getX() ? -1 : 1;

        int pixelStopValue = x - scrollUpButton.getWidth() + 1;

        if (direction == 1) {
          // If we're scrolling down, account for the width of the
          // handle in our pixel stop value so that we stop as soon
          // as the *bottom* of the handle reaches our click point
          pixelStopValue -= handle.getWidth();
        }

        realStopValue = (int) (pixelStopValue / getValueScale());
      } else {
        direction = y < handle.getY() ? -1 : 1;

        int pixelStopValue = y - scrollUpButton.getHeight() + 1;

        if (direction == 1) {
          // If we're scrolling down, account for the height of the
          // handle in our pixel stop value so that we stop as soon
          // as the *bottom* of the handle reaches our click point
          pixelStopValue -= handle.getHeight();
        }

        realStopValue = (int) (pixelStopValue / getValueScale());
      }

      // Start the automatic scroller
      automaticScroller.start(direction, Mouse.ScrollType.BLOCK, realStopValue);
      consumed = true;
    }

    return consumed;
  }
  @Override
  public void paint(Graphics2D graphics) {
    super.paint(graphics);

    ScrollBar scrollBar = (ScrollBar) getComponent();

    int width = getWidth();
    int height = getHeight();

    graphics.setPaint(borderColor);

    // Paint the scroll bar border lines
    if (scrollBar.getOrientation() == Orientation.HORIZONTAL) {
      int scrollUpButtonWidth = scrollUpButton.getWidth();
      int scrollDownButtonWidth = scrollDownButton.getWidth();

      GraphicsUtilities.drawLine(
          graphics,
          scrollUpButtonWidth,
          0,
          width - scrollDownButtonWidth - scrollUpButtonWidth,
          Orientation.HORIZONTAL);
      GraphicsUtilities.drawLine(
          graphics,
          scrollUpButtonWidth,
          height - 1,
          width - scrollDownButtonWidth - scrollUpButtonWidth,
          Orientation.HORIZONTAL);
    } else {
      int scrollUpButtonHeight = scrollUpButton.getHeight();
      int scrollDownButtonHeight = scrollDownButton.getHeight();

      GraphicsUtilities.drawLine(
          graphics,
          0,
          scrollUpButtonHeight,
          height - scrollDownButtonHeight - scrollUpButtonHeight,
          Orientation.VERTICAL);
      GraphicsUtilities.drawLine(
          graphics,
          width - 1,
          scrollUpButtonHeight,
          height - scrollDownButtonHeight - scrollUpButtonHeight,
          Orientation.VERTICAL);
    }
  }
    @Override
    public boolean mouseDown(Component component, Mouse.Button button, int x, int y) {
      boolean consumed = super.mouseDown(component, button, x, y);

      if (button == Mouse.Button.LEFT) {
        ScrollButton scrollButton = (ScrollButton) getComponent();

        // Start the automatic scroller. It'll be stopped when we
        // mouse up or mouse out
        automaticScroller.start(scrollButton.getDirection(), Mouse.ScrollType.UNIT, -1);

        pressed = true;
        repaintComponent();

        consumed = true;
      }

      return consumed;
    }
  @Override
  public void valueChanged(ScrollBar scrollBar, int previousValue) {
    // Invalidating the component would yield the correct behavior but
    // would be overkill. If all that has changed is the value, we can just
    // update the handle's location and save the work of full invalidation.
    if (handle.isVisible()) {
      int value = scrollBar.getValue();

      if (scrollBar.getOrientation() == Orientation.HORIZONTAL) {
        int handleX = (int) (value * getValueScale()) + scrollUpButton.getWidth() - 1;

        handle.setLocation(handleX, 0);
      } else {
        int handleY = (int) (value * getValueScale()) + scrollUpButton.getHeight() - 1;

        handle.setLocation(0, handleY);
      }
    }
  }
  @Override
  public void layout() {
    ScrollBar scrollBar = (ScrollBar) getComponent();

    int width = getWidth();
    int height = getHeight();

    int start = scrollBar.getStart();
    int end = scrollBar.getEnd();
    int extent = scrollBar.getExtent();
    int value = scrollBar.getValue();

    int maxLegalRealValue = end - extent;
    int numLegalRealValues = maxLegalRealValue - start + 1;
    float extentPercentage = (float) extent / (float) (end - start);

    if (scrollBar.getOrientation() == Orientation.HORIZONTAL) {
      scrollUpButton.setSize(scrollUpButton.getPreferredWidth(-1), height);
      scrollUpButton.setLocation(0, 0);

      scrollDownButton.setSize(scrollDownButton.getPreferredWidth(-1), height);
      scrollDownButton.setLocation(width - scrollDownButton.getWidth(), 0);

      if (scrollBar.isEnabled()) {
        // Calculate the handle width first, as it dictates how much
        // room is left to represent the range of legal values. Note
        // that the handle may overlap each scroll button by 1px so
        // that its borders merge into the borders of the scroll buttons
        int availableWidth = width - scrollUpButton.getWidth() - scrollDownButton.getWidth() + 2;
        int handleWidth =
            Math.max(minimumHandleLength, Math.round(extentPercentage * availableWidth));

        // Calculate the position of the handle by calculating the
        // scale that maps logical value to pixel value
        int numLegalPixelValues = availableWidth - handleWidth + 1;
        float valueScale = (float) numLegalPixelValues / (float) numLegalRealValues;
        int handleX = (int) (value * valueScale) + scrollUpButton.getWidth() - 1;

        if (handleWidth > availableWidth) {
          // If we can't fit the handle, we hide it
          handle.setVisible(false);
        } else {
          handle.setVisible(true);

          handle.setSize(handleWidth, height);
          handle.setLocation(handleX, 0);
        }
      } else {
        handle.setVisible(false);
      }
    } else {
      scrollUpButton.setSize(width, scrollUpButton.getPreferredHeight(-1));
      scrollUpButton.setLocation(0, 0);

      scrollDownButton.setSize(width, scrollDownButton.getPreferredHeight(-1));
      scrollDownButton.setLocation(0, height - scrollDownButton.getHeight());

      if (scrollBar.isEnabled()) {
        // Calculate the handle height first, as it dictates how much
        // room is left to represent the range of legal values. Note
        // that the handle may overlap each scroll button by 1px so
        // that its borders merge into the borders of the scroll buttons
        int availableHeight =
            height - scrollUpButton.getHeight() - scrollDownButton.getHeight() + 2;
        int handleHeight =
            Math.max(minimumHandleLength, Math.round(extentPercentage * availableHeight));

        // Calculate the position of the handle by calculating the
        // scale maps logical value to pixel value
        int numLegalPixelValues = availableHeight - handleHeight + 1;
        float valueScale = (float) numLegalPixelValues / (float) numLegalRealValues;
        int handleY = (int) (value * valueScale) + scrollUpButton.getHeight() - 1;

        if (handleHeight > availableHeight) {
          // If we can't fit the handle, we hide it
          handle.setVisible(false);
        } else {
          handle.setVisible(true);

          handle.setSize(width, handleHeight);
          handle.setLocation(0, handleY);
        }
      } else {
        handle.setVisible(false);
      }
    }
  }