@Override
    public void paint(Graphics2D graphics) {
      ScrollBar scrollBar = (ScrollBar) getComponent();

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

      graphics.setPaint(scrollButtonImageColor);
      graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

      GeneralPath arrow = new GeneralPath(GeneralPath.WIND_EVEN_ODD);

      if (scrollBar.getOrientation() == Orientation.HORIZONTAL) {
        arrow.moveTo(0, 0);
        arrow.lineTo(width + 0.5f, height / 2.0f);
        arrow.lineTo(0, height);
      } else {
        arrow.moveTo(0, 0);
        arrow.lineTo(width / 2.0f, height + 0.5f);
        arrow.lineTo(width, 0);
      }

      arrow.closePath();
      // TODO Use Graphics#fillPolygon() as optimization?
      graphics.fill(arrow);
    }
  @Override
  public void install(Component component) {
    super.install(component);

    ScrollBar scrollBar = (ScrollBar) component;
    scrollBar.getScrollBarListeners().add(this);
    scrollBar.getScrollBarValueListeners().add(this);

    scrollBar.add(scrollUpButton);
    scrollBar.add(scrollDownButton);
    scrollBar.add(handle);

    TerraTheme theme = (TerraTheme) Theme.getTheme();

    Color backgroundColor = theme.getColor(9);
    Color brightBackgroundColor = TerraTheme.brighten(backgroundColor);

    GradientPaint backgroundPaint;
    if (scrollBar.getOrientation() == Orientation.HORIZONTAL) {
      backgroundPaint =
          new GradientPaint(0, 1, backgroundColor, 0, DEFAULT_THICKNESS - 2, brightBackgroundColor);
    } else {
      backgroundPaint =
          new GradientPaint(1, 0, backgroundColor, DEFAULT_THICKNESS - 2, 0, brightBackgroundColor);
    }

    setBackgroundPaint(backgroundPaint);

    enabledChanged(scrollBar);
  }
    @Override
    public boolean mouseMove(Component component, int x, int y) {
      boolean consumed = super.mouseMove(component, x, y);

      if (Mouse.getCapturer() == component) {
        ScrollBar scrollBar = (ScrollBar) TerraScrollBarSkin.this.getComponent();
        Orientation orientation = scrollBar.getOrientation();

        // Calculate the new scroll bar value
        int pixelValue;
        if (orientation == Orientation.HORIZONTAL) {
          pixelValue = component.getX() - scrollUpButton.getWidth() + x - dragOffset;
        } else {
          pixelValue = component.getY() - scrollUpButton.getHeight() + y - dragOffset;
        }

        int realValue = (int) (pixelValue / getValueScale());

        // Bound the value
        int end = scrollBar.getEnd();
        int extent = scrollBar.getExtent();
        realValue = Math.min(Math.max(realValue, 0), end - extent);

        // Update the scroll bar
        scrollBar.setValue(realValue);
      }

      return consumed;
    }
  /**
   * 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();
    }
    @Override
    public void paint(Graphics2D graphics) {
      ScrollBar scrollBar = (ScrollBar) TerraScrollBarSkin.this.getComponent();
      Orientation orientation = scrollBar.getOrientation();

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

      // Paint the background
      Color backgroundColor =
          highlighted ? scrollButtonHighlightedBackgroundColor : scrollButtonBackgroundColor;

      Color brightBackgroundColor = TerraTheme.brighten(backgroundColor);
      Color darkBackgroundColor = TerraTheme.darken(backgroundColor);

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

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

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

      // Paint the hash marks
      if (orientation == Orientation.HORIZONTAL) {
        int middle = width / 2;
        graphics.setPaint(darkBackgroundColor);
        graphics.drawLine(middle - 3, 4, middle - 3, height - 5);
        graphics.drawLine(middle, 4, middle, height - 5);
        graphics.drawLine(middle + 3, 4, middle + 3, height - 5);
        graphics.setPaint(brightBackgroundColor);
        graphics.drawLine(middle - 2, 4, middle - 2, height - 5);
        graphics.drawLine(middle + 1, 4, middle + 1, height - 5);
        graphics.drawLine(middle + 4, 4, middle + 4, height - 5);
      } else {
        int middle = height / 2;
        graphics.setPaint(darkBackgroundColor);
        graphics.drawLine(4, middle - 3, width - 5, middle - 3);
        graphics.drawLine(4, middle, width - 5, middle);
        graphics.drawLine(4, middle + 3, width - 5, middle + 3);
        graphics.setPaint(brightBackgroundColor);
        graphics.drawLine(4, middle - 2, width - 5, middle - 2);
        graphics.drawLine(4, middle + 1, width - 5, middle + 1);
        graphics.drawLine(4, middle + 4, width - 5, middle + 4);
      }
    }
  @Override
  public int getPreferredHeight(int width) {
    ScrollBar scrollBar = (ScrollBar) getComponent();

    int preferredHeight = 0;

    if (scrollBar.getOrientation() == Orientation.HORIZONTAL) {
      preferredHeight = DEFAULT_THICKNESS;
    } else {
      preferredHeight = DEFAULT_LENGTH;
    }

    return preferredHeight;
  }
    @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) {
        ScrollBar scrollBar = (ScrollBar) TerraScrollBarSkin.this.getComponent();
        Orientation orientation = scrollBar.getOrientation();

        dragOffset = (orientation == Orientation.HORIZONTAL ? x : y);
        Mouse.capture(component);
        consumed = true;
      }

      return consumed;
    }
  /**
   * 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 Dimensions getPreferredSize() {
    ScrollBar scrollBar = (ScrollBar) getComponent();

    int preferredWidth = 0;
    int preferredHeight = 0;

    if (scrollBar.getOrientation() == Orientation.HORIZONTAL) {
      preferredWidth = DEFAULT_LENGTH;
      preferredHeight = DEFAULT_THICKNESS;
    } else {
      preferredWidth = DEFAULT_THICKNESS;
      preferredHeight = DEFAULT_LENGTH;
    }

    return new Dimensions(preferredWidth, preferredHeight);
  }
  @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);
      }
    }
  }
 @Override
 public int getHeight() {
   ScrollBar scrollBar = (ScrollBar) getComponent();
   Orientation orientation = scrollBar.getOrientation();
   return (orientation == Orientation.HORIZONTAL ? 7 : 5);
 }