1. /*
  2. * @(#)BasicScrollBarUI.java 1.75 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.plaf.basic;
  8. import java.awt.*;
  9. import java.awt.event.*;
  10. import java.beans.*;
  11. import javax.swing.*;
  12. import javax.swing.event.*;
  13. import javax.swing.plaf.*;
  14. /**
  15. * Implementation of ScrollBarUI for the Basic Look and Feel
  16. *
  17. * @version 1.75 01/23/03
  18. * @author Rich Schiavi
  19. * @author David Kloba
  20. * @author Hans Muller
  21. */
  22. public class BasicScrollBarUI
  23. extends ScrollBarUI implements LayoutManager, SwingConstants
  24. {
  25. private static final int POSITIVE_SCROLL = 1;
  26. private static final int NEGATIVE_SCROLL = -1;
  27. private static final int MIN_SCROLL = 2;
  28. private static final int MAX_SCROLL = 3;
  29. protected Dimension minimumThumbSize;
  30. protected Dimension maximumThumbSize;
  31. protected Color thumbHighlightColor;
  32. protected Color thumbLightShadowColor;
  33. protected Color thumbDarkShadowColor;
  34. protected Color thumbColor;
  35. protected Color trackColor;
  36. protected Color trackHighlightColor;
  37. protected JScrollBar scrollbar;
  38. protected JButton incrButton;
  39. protected JButton decrButton;
  40. protected boolean isDragging;
  41. protected TrackListener trackListener;
  42. protected ArrowButtonListener buttonListener;
  43. protected ModelListener modelListener;
  44. protected Rectangle thumbRect;
  45. protected Rectangle trackRect;
  46. protected int trackHighlight;
  47. protected static final int NO_HIGHLIGHT = 0;
  48. protected static final int DECREASE_HIGHLIGHT = 1;
  49. protected static final int INCREASE_HIGHLIGHT = 2;
  50. protected ScrollListener scrollListener;
  51. protected PropertyChangeListener propertyChangeListener;
  52. protected Timer scrollTimer;
  53. private final static int scrollSpeedThrottle = 60; // delay in milli seconds
  54. /** True indicates a middle click will absolutely position the
  55. * scrollbar. */
  56. private boolean supportsAbsolutePositioning;
  57. /** Hint as to what width (when vertical) or height (when horizontal)
  58. * should be.
  59. */
  60. private int scrollBarWidth;
  61. public static ComponentUI createUI(JComponent c) {
  62. return new BasicScrollBarUI();
  63. }
  64. protected void configureScrollBarColors()
  65. {
  66. LookAndFeel.installColors(scrollbar, "ScrollBar.background",
  67. "ScrollBar.foreground");
  68. thumbHighlightColor = UIManager.getColor("ScrollBar.thumbHighlight");
  69. thumbLightShadowColor = UIManager.getColor("ScrollBar.thumbShadow");
  70. thumbDarkShadowColor = UIManager.getColor("ScrollBar.thumbDarkShadow");
  71. thumbColor = UIManager.getColor("ScrollBar.thumb");
  72. trackColor = UIManager.getColor("ScrollBar.track");
  73. trackHighlightColor = UIManager.getColor("ScrollBar.trackHighlight");
  74. }
  75. public void installUI(JComponent c) {
  76. scrollbar = (JScrollBar)c;
  77. thumbRect = new Rectangle(0, 0, 0, 0);
  78. trackRect = new Rectangle(0, 0, 0, 0);
  79. installDefaults();
  80. installComponents();
  81. installListeners();
  82. installKeyboardActions();
  83. }
  84. public void uninstallUI(JComponent c) {
  85. scrollbar = (JScrollBar)c;
  86. uninstallDefaults();
  87. uninstallComponents();
  88. uninstallListeners();
  89. uninstallKeyboardActions();
  90. c.remove(incrButton);
  91. c.remove(decrButton);
  92. c.setLayout(null);
  93. thumbRect = null;
  94. scrollbar = null;
  95. incrButton = null;
  96. decrButton = null;
  97. }
  98. protected void installDefaults()
  99. {
  100. scrollBarWidth = UIManager.getInt("ScrollBar.width");
  101. if (scrollBarWidth <= 0) {
  102. scrollBarWidth = 16;
  103. }
  104. minimumThumbSize = (Dimension)UIManager.get("ScrollBar.minimumThumbSize");
  105. maximumThumbSize = (Dimension)UIManager.get("ScrollBar.maximumThumbSize");
  106. Boolean absB = (Boolean)UIManager.get("ScrollBar.allowsAbsolutePositioning");
  107. supportsAbsolutePositioning = (absB != null) ? absB.booleanValue() :
  108. false;
  109. trackHighlight = NO_HIGHLIGHT;
  110. switch (scrollbar.getOrientation()) {
  111. case JScrollBar.VERTICAL:
  112. incrButton = createIncreaseButton(SOUTH);
  113. decrButton = createDecreaseButton(NORTH);
  114. break;
  115. case JScrollBar.HORIZONTAL:
  116. if (scrollbar.getComponentOrientation().isLeftToRight()) {
  117. incrButton = createIncreaseButton(EAST);
  118. decrButton = createDecreaseButton(WEST);
  119. } else {
  120. incrButton = createIncreaseButton(WEST);
  121. decrButton = createDecreaseButton(EAST);
  122. }
  123. break;
  124. }
  125. scrollbar.setLayout(this);
  126. scrollbar.add(incrButton);
  127. scrollbar.add(decrButton);
  128. scrollbar.setEnabled(scrollbar.isEnabled());
  129. scrollbar.setOpaque(true);
  130. configureScrollBarColors();
  131. LookAndFeel.installBorder(scrollbar, "ScrollBar.border");
  132. }
  133. protected void installComponents(){
  134. }
  135. protected void uninstallComponents(){
  136. }
  137. protected void installListeners(){
  138. trackListener = createTrackListener();
  139. buttonListener = createArrowButtonListener();
  140. modelListener = createModelListener();
  141. propertyChangeListener = createPropertyChangeListener();
  142. scrollbar.addMouseListener(trackListener);
  143. scrollbar.addMouseMotionListener(trackListener);
  144. scrollbar.getModel().addChangeListener(modelListener);
  145. scrollbar.addPropertyChangeListener(propertyChangeListener);
  146. if (incrButton != null) {
  147. incrButton.addMouseListener(buttonListener);
  148. }
  149. if (decrButton != null) {
  150. decrButton.addMouseListener(buttonListener);
  151. }
  152. scrollListener = createScrollListener();
  153. scrollTimer = new Timer(scrollSpeedThrottle, scrollListener);
  154. scrollTimer.setInitialDelay(300); // default InitialDelay?
  155. }
  156. protected void installKeyboardActions(){
  157. ActionMap map = getActionMap();
  158. SwingUtilities.replaceUIActionMap(scrollbar, map);
  159. InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED);
  160. SwingUtilities.replaceUIInputMap(scrollbar, JComponent.WHEN_FOCUSED,
  161. inputMap);
  162. }
  163. protected void uninstallKeyboardActions(){
  164. SwingUtilities.replaceUIInputMap(scrollbar, JComponent.WHEN_FOCUSED,
  165. null);
  166. SwingUtilities.replaceUIActionMap(scrollbar, null);
  167. }
  168. private InputMap getInputMap(int condition) {
  169. if (condition == JComponent.WHEN_FOCUSED) {
  170. InputMap keyMap = (InputMap)UIManager.get("ScrollBar.focusInputMap");
  171. InputMap rtlKeyMap;
  172. if (scrollbar.getComponentOrientation().isLeftToRight() ||
  173. ((rtlKeyMap = (InputMap)UIManager.get("ScrollBar.focusInputMap.RightToLeft")) == null)) {
  174. return keyMap;
  175. } else {
  176. rtlKeyMap.setParent(keyMap);
  177. return rtlKeyMap;
  178. }
  179. }
  180. return null;
  181. }
  182. private ActionMap getActionMap() {
  183. ActionMap map = (ActionMap)UIManager.get("ScrollBar.actionMap");
  184. if (map == null) {
  185. map = createActionMap();
  186. if (map != null) {
  187. UIManager.getLookAndFeelDefaults().put("ScrollBar.actionMap",
  188. map);
  189. }
  190. }
  191. return map;
  192. }
  193. private ActionMap createActionMap() {
  194. ActionMap map = new ActionMapUIResource();
  195. map.put("positiveUnitIncrement", new SharedActionScroller
  196. (POSITIVE_SCROLL, false));
  197. map.put("positiveBlockIncrement", new SharedActionScroller
  198. (POSITIVE_SCROLL, true));
  199. map.put("negativeUnitIncrement", new SharedActionScroller
  200. (NEGATIVE_SCROLL, false));
  201. map.put("negativeBlockIncrement", new SharedActionScroller
  202. (NEGATIVE_SCROLL, true));
  203. map.put("minScroll", new SharedActionScroller(MIN_SCROLL, true));
  204. map.put("maxScroll", new SharedActionScroller(MAX_SCROLL, true));
  205. return map;
  206. }
  207. protected void uninstallListeners() {
  208. scrollTimer.stop();
  209. scrollTimer = null;
  210. if (decrButton != null){
  211. decrButton.removeMouseListener(buttonListener);
  212. }
  213. if (incrButton != null){
  214. incrButton.removeMouseListener(buttonListener);
  215. }
  216. scrollbar.getModel().removeChangeListener(modelListener);
  217. scrollbar.removeMouseListener(trackListener);
  218. scrollbar.removeMouseMotionListener(trackListener);
  219. scrollbar.removePropertyChangeListener(propertyChangeListener);
  220. }
  221. protected void uninstallDefaults(){
  222. LookAndFeel.uninstallBorder(scrollbar);
  223. }
  224. protected TrackListener createTrackListener(){
  225. return new TrackListener();
  226. }
  227. protected ArrowButtonListener createArrowButtonListener(){
  228. return new ArrowButtonListener();
  229. }
  230. protected ModelListener createModelListener(){
  231. return new ModelListener();
  232. }
  233. protected ScrollListener createScrollListener(){
  234. return new ScrollListener();
  235. }
  236. protected PropertyChangeListener createPropertyChangeListener() {
  237. return new PropertyChangeHandler();
  238. }
  239. public void paint(Graphics g, JComponent c) {
  240. paintTrack(g, c, getTrackBounds());
  241. paintThumb(g, c, getThumbBounds());
  242. }
  243. /**
  244. * A vertical scrollbar's preferred width is the maximum of
  245. * preferred widths of the (non <code>null</code>)
  246. * increment/decrement buttons,
  247. * and the minimum width of the thumb. The preferred height is the
  248. * sum of the preferred heights of the same parts. The basis for
  249. * the preferred size of a horizontal scrollbar is similar.
  250. * <p>
  251. * The <code>preferredSize</code> is only computed once, subsequent
  252. * calls to this method just return a cached size.
  253. *
  254. * @param c the <code>JScrollBar</code> that's delegating this method to us
  255. * @return the preferred size of a Basic JScrollBar
  256. * @see #getMaximumSize
  257. * @see #getMinimumSize
  258. */
  259. public Dimension getPreferredSize(JComponent c) {
  260. return (scrollbar.getOrientation() == JScrollBar.VERTICAL)
  261. ? new Dimension(scrollBarWidth, 48)
  262. : new Dimension(48, scrollBarWidth);
  263. }
  264. /**
  265. * A vertical scrollbar's minimum width is the largest
  266. * minimum width of the (non <code>null</code>) increment/decrement buttons,
  267. * and the minimum width of the thumb. The minimum height is the
  268. * sum of the minimum heights of the same parts. The basis for
  269. * the preferred size of a horizontal scrollbar is similar.
  270. * <p>
  271. * The <code>minimumSize</code> is only computed once, subsequent
  272. * calls to this method just return a cached size.
  273. *
  274. * @param c the <code>JScrollBar</code> that's delegating this method to us
  275. * @return the minimum size of a basic <code>JScrollBar</code>
  276. * @see #getMaximumSize
  277. * @see #getPreferredSize
  278. */
  279. public Dimension getMinimumSize(JComponent c) {
  280. return getPreferredSize(c);
  281. }
  282. /**
  283. * @param c The JScrollBar that's delegating this method to us.
  284. * @return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
  285. * @see #getMinimumSize
  286. * @see #getPreferredSize
  287. */
  288. public Dimension getMaximumSize(JComponent c) {
  289. return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
  290. }
  291. protected JButton createDecreaseButton(int orientation) {
  292. return new BasicArrowButton(orientation,
  293. UIManager.getColor("ScrollBar.thumb"),
  294. UIManager.getColor("ScrollBar.thumbShadow"),
  295. UIManager.getColor("ScrollBar.thumbDarkShadow"),
  296. UIManager.getColor("ScrollBar.thumbHighlight"));
  297. }
  298. protected JButton createIncreaseButton(int orientation) {
  299. return new BasicArrowButton(orientation,
  300. UIManager.getColor("ScrollBar.thumb"),
  301. UIManager.getColor("ScrollBar.thumbShadow"),
  302. UIManager.getColor("ScrollBar.thumbDarkShadow"),
  303. UIManager.getColor("ScrollBar.thumbHighlight"));
  304. }
  305. protected void paintDecreaseHighlight(Graphics g)
  306. {
  307. Insets insets = scrollbar.getInsets();
  308. Rectangle thumbR = getThumbBounds();
  309. g.setColor(trackHighlightColor);
  310. if (scrollbar.getOrientation() == JScrollBar.VERTICAL) {
  311. int x = insets.left;
  312. int y = decrButton.getY() + decrButton.getHeight();
  313. int w = scrollbar.getWidth() - (insets.left + insets.right);
  314. int h = thumbR.y - y;
  315. g.fillRect(x, y, w, h);
  316. }
  317. else {
  318. int x, w;
  319. if (scrollbar.getComponentOrientation().isLeftToRight()) {
  320. x = decrButton.getX() + decrButton.getWidth();
  321. w = thumbR.x - x;
  322. } else {
  323. x = thumbR.x + thumbR.width;
  324. w = decrButton.getX() - x;
  325. }
  326. int y = insets.top;
  327. int h = scrollbar.getHeight() - (insets.top + insets.bottom);
  328. g.fillRect(x, y, w, h);
  329. }
  330. }
  331. protected void paintIncreaseHighlight(Graphics g)
  332. {
  333. Insets insets = scrollbar.getInsets();
  334. Rectangle thumbR = getThumbBounds();
  335. g.setColor(trackHighlightColor);
  336. if (scrollbar.getOrientation() == JScrollBar.VERTICAL) {
  337. int x = insets.left;
  338. int y = thumbR.y + thumbR.height;
  339. int w = scrollbar.getWidth() - (insets.left + insets.right);
  340. int h = incrButton.getY() - y;
  341. g.fillRect(x, y, w, h);
  342. }
  343. else {
  344. int x, w;
  345. if (scrollbar.getComponentOrientation().isLeftToRight()) {
  346. x = thumbR.x + thumbR.width;
  347. w = incrButton.getX() - x;
  348. } else {
  349. x = incrButton.getX() + incrButton.getWidth();
  350. w = thumbR.x - x;
  351. }
  352. int y = insets.top;
  353. int h = scrollbar.getHeight() - (insets.top + insets.bottom);
  354. g.fillRect(x, y, w, h);
  355. }
  356. }
  357. protected void paintTrack(Graphics g, JComponent c, Rectangle trackBounds)
  358. {
  359. g.setColor(trackColor);
  360. g.fillRect(trackBounds.x, trackBounds.y, trackBounds.width, trackBounds.height);
  361. if(trackHighlight == DECREASE_HIGHLIGHT) {
  362. paintDecreaseHighlight(g);
  363. }
  364. else if(trackHighlight == INCREASE_HIGHLIGHT) {
  365. paintIncreaseHighlight(g);
  366. }
  367. }
  368. protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds)
  369. {
  370. if(thumbBounds.isEmpty() || !scrollbar.isEnabled()) {
  371. return;
  372. }
  373. int w = thumbBounds.width;
  374. int h = thumbBounds.height;
  375. g.translate(thumbBounds.x, thumbBounds.y);
  376. g.setColor(thumbDarkShadowColor);
  377. g.drawRect(0, 0, w-1, h-1);
  378. g.setColor(thumbColor);
  379. g.fillRect(0, 0, w-1, h-1);
  380. g.setColor(thumbHighlightColor);
  381. g.drawLine(1, 1, 1, h-2);
  382. g.drawLine(2, 1, w-3, 1);
  383. g.setColor(thumbLightShadowColor);
  384. g.drawLine(2, h-2, w-2, h-2);
  385. g.drawLine(w-2, 1, w-2, h-3);
  386. g.translate(-thumbBounds.x, -thumbBounds.y);
  387. }
  388. /**
  389. * Return the smallest acceptable size for the thumb. If the scrollbar
  390. * becomes so small that this size isn't available, the thumb will be
  391. * hidden.
  392. * <p>
  393. * <b>Warning </b>: the value returned by this method should not be
  394. * be modified, it's a shared static constant.
  395. *
  396. * @return The smallest acceptable size for the thumb.
  397. * @see #getMaximumThumbSize
  398. */
  399. protected Dimension getMinimumThumbSize() {
  400. return minimumThumbSize;
  401. }
  402. /**
  403. * Return the largest acceptable size for the thumb. To create a fixed
  404. * size thumb one make this method and <code>getMinimumThumbSize</code>
  405. * return the same value.
  406. * <p>
  407. * <b>Warning </b>: the value returned by this method should not be
  408. * be modified, it's a shared static constant.
  409. *
  410. * @return The largest acceptable size for the thumb.
  411. * @see #getMinimumThumbSize
  412. */
  413. protected Dimension getMaximumThumbSize() {
  414. return maximumThumbSize;
  415. }
  416. /*
  417. * LayoutManager Implementation
  418. */
  419. public void addLayoutComponent(String name, Component child) {}
  420. public void removeLayoutComponent(Component child) {}
  421. public Dimension preferredLayoutSize(Container scrollbarContainer) {
  422. return getPreferredSize((JComponent)scrollbarContainer);
  423. }
  424. public Dimension minimumLayoutSize(Container scrollbarContainer) {
  425. return getMinimumSize((JComponent)scrollbarContainer);
  426. }
  427. protected void layoutVScrollbar(JScrollBar sb)
  428. {
  429. Dimension sbSize = sb.getSize();
  430. Insets sbInsets = sb.getInsets();
  431. /*
  432. * Width and left edge of the buttons and thumb.
  433. */
  434. int itemW = sbSize.width - (sbInsets.left + sbInsets.right);
  435. int itemX = sbInsets.left;
  436. /* Nominal locations of the buttons, assuming their preferred
  437. * size will fit.
  438. */
  439. int decrButtonH = decrButton.getPreferredSize().height;
  440. int decrButtonY = sbInsets.top;
  441. int incrButtonH = incrButton.getPreferredSize().height;
  442. int incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH);
  443. /* The thumb must fit within the height left over after we
  444. * subtract the preferredSize of the buttons and the insets.
  445. */
  446. int sbInsetsH = sbInsets.top + sbInsets.bottom;
  447. int sbButtonsH = decrButtonH + incrButtonH;
  448. float trackH = sbSize.height - (sbInsetsH + sbButtonsH);
  449. /* Compute the height and origin of the thumb. The case
  450. * where the thumb is at the bottom edge is handled specially
  451. * to avoid numerical problems in computing thumbY. Enforce
  452. * the thumbs min/max dimensions. If the thumb doesn't
  453. * fit in the track (trackH) we'll hide it later.
  454. */
  455. float min = sb.getMinimum();
  456. float extent = sb.getVisibleAmount();
  457. float range = sb.getMaximum() - min;
  458. float value = sb.getValue();
  459. int thumbH = (range <= 0)
  460. ? getMaximumThumbSize().height : (int)(trackH * (extent / range));
  461. thumbH = Math.max(thumbH, getMinimumThumbSize().height);
  462. thumbH = Math.min(thumbH, getMaximumThumbSize().height);
  463. int thumbY = incrButtonY - thumbH;
  464. if (sb.getValue() < (sb.getMaximum() - sb.getVisibleAmount())) {
  465. float thumbRange = trackH - thumbH;
  466. thumbY = (int)(0.5f + (thumbRange * ((value - min) / (range - extent))));
  467. thumbY += decrButtonY + decrButtonH;
  468. }
  469. /* If the buttons don't fit, allocate half of the available
  470. * space to each and move the lower one (incrButton) down.
  471. */
  472. int sbAvailButtonH = (sbSize.height - sbInsetsH);
  473. if (sbAvailButtonH < sbButtonsH) {
  474. incrButtonH = decrButtonH = sbAvailButtonH / 2;
  475. incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH);
  476. }
  477. decrButton.setBounds(itemX, decrButtonY, itemW, decrButtonH);
  478. incrButton.setBounds(itemX, incrButtonY, itemW, incrButtonH);
  479. /* Update the trackRect field.
  480. */
  481. int itrackY = decrButtonY + decrButtonH;
  482. int itrackH = incrButtonY - itrackY;
  483. trackRect.setBounds(itemX, itrackY, itemW, itrackH);
  484. /* If the thumb isn't going to fit, zero it's bounds. Otherwise
  485. * make sure it fits between the buttons. Note that setting the
  486. * thumbs bounds will cause a repaint.
  487. */
  488. if(thumbH >= (int)trackH) {
  489. setThumbBounds(0, 0, 0, 0);
  490. }
  491. else {
  492. if ((thumbY + thumbH) > incrButtonY) {
  493. thumbY = incrButtonY - thumbH;
  494. }
  495. if (thumbY < (decrButtonY + decrButtonH)) {
  496. thumbY = decrButtonY + decrButtonH + 1;
  497. }
  498. setThumbBounds(itemX, thumbY, itemW, thumbH);
  499. }
  500. }
  501. protected void layoutHScrollbar(JScrollBar sb)
  502. {
  503. Dimension sbSize = sb.getSize();
  504. Insets sbInsets = sb.getInsets();
  505. /* Height and top edge of the buttons and thumb.
  506. */
  507. int itemH = sbSize.height - (sbInsets.top + sbInsets.bottom);
  508. int itemY = sbInsets.top;
  509. boolean ltr = sb.getComponentOrientation().isLeftToRight();
  510. /* Nominal locations of the buttons, assuming their preferred
  511. * size will fit.
  512. */
  513. int leftButtonW = (ltr ? decrButton : incrButton).getPreferredSize().width;
  514. int rightButtonW = (ltr ? incrButton : decrButton).getPreferredSize().width;
  515. int leftButtonX = sbInsets.left;
  516. int rightButtonX = sbSize.width - (sbInsets.right + rightButtonW);
  517. /* The thumb must fit within the width left over after we
  518. * subtract the preferredSize of the buttons and the insets.
  519. */
  520. int sbInsetsW = sbInsets.left + sbInsets.right;
  521. int sbButtonsW = leftButtonW + rightButtonW;
  522. float trackW = sbSize.width - (sbInsetsW + sbButtonsW);
  523. /* Compute the width and origin of the thumb. Enforce
  524. * the thumbs min/max dimensions. The case where the thumb
  525. * is at the right edge is handled specially to avoid numerical
  526. * problems in computing thumbX. If the thumb doesn't
  527. * fit in the track (trackH) we'll hide it later.
  528. */
  529. float min = sb.getMinimum();
  530. float max = sb.getMaximum();
  531. float extent = sb.getVisibleAmount();
  532. float range = max - min;
  533. float value = sb.getValue();
  534. int thumbW = (range <= 0)
  535. ? getMaximumThumbSize().width : (int)(trackW * (extent / range));
  536. thumbW = Math.max(thumbW, getMinimumThumbSize().width);
  537. thumbW = Math.min(thumbW, getMaximumThumbSize().width);
  538. int thumbX = ltr ? rightButtonX - thumbW : leftButtonX + leftButtonW;
  539. if (sb.getValue() < (max - sb.getVisibleAmount())) {
  540. float thumbRange = trackW - thumbW;
  541. if( ltr ) {
  542. thumbX = (int)(0.5f + (thumbRange * ((value - min) / (range - extent))));
  543. } else {
  544. thumbX = (int)(0.5f + (thumbRange * ((max - extent - value) / (range - extent))));
  545. }
  546. thumbX += leftButtonX + leftButtonW;
  547. }
  548. /* If the buttons don't fit, allocate half of the available
  549. * space to each and move the right one over.
  550. */
  551. int sbAvailButtonW = (sbSize.width - sbInsetsW);
  552. if (sbAvailButtonW < sbButtonsW) {
  553. rightButtonW = leftButtonW = sbAvailButtonW / 2;
  554. rightButtonX = sbSize.width - (sbInsets.right + rightButtonW);
  555. }
  556. (ltr ? decrButton : incrButton).setBounds(leftButtonX, itemY, leftButtonW, itemH);
  557. (ltr ? incrButton : decrButton).setBounds(rightButtonX, itemY, rightButtonW, itemH);
  558. /* Update the trackRect field.
  559. */
  560. int itrackX = leftButtonX + leftButtonW;
  561. int itrackW = rightButtonX - itrackX;
  562. trackRect.setBounds(itrackX, itemY, itrackW, itemH);
  563. /* Make sure the thumb fits between the buttons. Note
  564. * that setting the thumbs bounds causes a repaint.
  565. */
  566. if (thumbW >= (int)trackW) {
  567. setThumbBounds(0, 0, 0, 0);
  568. }
  569. else {
  570. if (thumbX + thumbW > rightButtonX) {
  571. thumbX = rightButtonX - thumbW;
  572. }
  573. if (thumbX < leftButtonX + leftButtonW) {
  574. thumbX = leftButtonX + leftButtonW + 1;
  575. }
  576. setThumbBounds(thumbX, itemY, thumbW, itemH);
  577. }
  578. }
  579. public void layoutContainer(Container scrollbarContainer)
  580. {
  581. /* If the user is dragging the value, we'll assume that the
  582. * scrollbars layout is OK modulo the thumb which is being
  583. * handled by the dragging code.
  584. */
  585. if (isDragging) {
  586. return;
  587. }
  588. JScrollBar scrollbar = (JScrollBar)scrollbarContainer;
  589. switch (scrollbar.getOrientation()) {
  590. case JScrollBar.VERTICAL:
  591. layoutVScrollbar(scrollbar);
  592. break;
  593. case JScrollBar.HORIZONTAL:
  594. layoutHScrollbar(scrollbar);
  595. break;
  596. }
  597. }
  598. /**
  599. * Set the bounds of the thumb and force a repaint that includes
  600. * the old thumbBounds and the new one.
  601. *
  602. * @see #getThumbBounds
  603. */
  604. protected void setThumbBounds(int x, int y, int width, int height)
  605. {
  606. /* If the thumbs bounds haven't changed, we're done.
  607. */
  608. if ((thumbRect.x == x) &&
  609. (thumbRect.y == y) &&
  610. (thumbRect.width == width) &&
  611. (thumbRect.height == height)) {
  612. return;
  613. }
  614. /* Update thumbRect, and repaint the union of x,y,w,h and
  615. * the old thumbRect.
  616. */
  617. int minX = Math.min(x, thumbRect.x);
  618. int minY = Math.min(y, thumbRect.y);
  619. int maxX = Math.max(x + width, thumbRect.x + thumbRect.width);
  620. int maxY = Math.max(y + height, thumbRect.y + thumbRect.height);
  621. thumbRect.setBounds(x, y, width, height);
  622. scrollbar.repaint(minX, minY, maxX - minX, maxY - minY);
  623. }
  624. /**
  625. * Return the current size/location of the thumb.
  626. * <p>
  627. * <b>Warning </b>: the value returned by this method should not be
  628. * be modified, it's a reference to the actual rectangle, not a copy.
  629. *
  630. * @return The current size/location of the thumb.
  631. * @see #setThumbBounds
  632. */
  633. protected Rectangle getThumbBounds() {
  634. return thumbRect;
  635. }
  636. /**
  637. * Returns the current bounds of the track, i.e. the space in between
  638. * the increment and decrement buttons, less the insets. The value
  639. * returned by this method is updated each time the scrollbar is
  640. * laid out (validated).
  641. * <p>
  642. * <b>Warning </b>: the value returned by this method should not be
  643. * be modified, it's a reference to the actual rectangle, not a copy.
  644. *
  645. * @return the current bounds of the scrollbar track
  646. * @see #layoutContainer
  647. */
  648. protected Rectangle getTrackBounds() {
  649. return trackRect;
  650. }
  651. /*
  652. * Method for scrolling by a block increment.
  653. * Added for mouse wheel scrolling support, RFE 4202656.
  654. */
  655. static void scrollByBlock(JScrollBar scrollbar, int direction) {
  656. // This method is called from BasicScrollPaneUI to implement wheel
  657. // scrolling, and also from scrollByBlock().
  658. int oldValue = scrollbar.getValue();
  659. int blockIncrement = scrollbar.getBlockIncrement(direction);
  660. int delta = blockIncrement * ((direction > 0) ? +1 : -1);
  661. scrollbar.setValue(oldValue + delta);
  662. }
  663. protected void scrollByBlock(int direction)
  664. {
  665. scrollByBlock(scrollbar, direction);
  666. trackHighlight = direction > 0 ? INCREASE_HIGHLIGHT : DECREASE_HIGHLIGHT;
  667. Rectangle dirtyRect = getTrackBounds();
  668. scrollbar.repaint(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height);
  669. }
  670. /*
  671. * Method for scrolling by a unit increment.
  672. * Added for mouse wheel scrolling support, RFE 4202656.
  673. */
  674. static void scrollByUnits(JScrollBar scrollbar, int direction,
  675. int units) {
  676. // This method is called from BasicScrollPaneUI to implement wheel
  677. // scrolling, as well as from scrollByUnit().
  678. int delta = units;
  679. if (direction > 0) {
  680. delta *= scrollbar.getUnitIncrement(direction);
  681. }
  682. else {
  683. delta *= -scrollbar.getUnitIncrement(direction);
  684. }
  685. int oldValue = scrollbar.getValue();
  686. int newValue = oldValue + delta;
  687. // Check for overflow.
  688. if (delta > 0 && newValue < oldValue) {
  689. newValue = scrollbar.getMaximum();
  690. }
  691. else if (delta < 0 && newValue > oldValue) {
  692. newValue = scrollbar.getMinimum();
  693. }
  694. scrollbar.setValue(newValue);
  695. }
  696. protected void scrollByUnit(int direction) {
  697. scrollByUnits(scrollbar, direction, 1);
  698. }
  699. /**
  700. * Indicates whether the user can absolutely position the offset with
  701. * a mouse click (usually the middle mouse button).
  702. * <p>The return value is determined from the UIManager property
  703. * ScrollBar.allowsAbsolutePositioning.
  704. */
  705. private boolean getSupportsAbsolutePositioning() {
  706. return supportsAbsolutePositioning;
  707. }
  708. /**
  709. * A listener to listen for model changes.
  710. *
  711. */
  712. protected class ModelListener implements ChangeListener {
  713. public void stateChanged(ChangeEvent e) {
  714. layoutContainer(scrollbar);
  715. }
  716. }
  717. /**
  718. * Track mouse drags.
  719. */
  720. protected class TrackListener
  721. extends MouseAdapter implements MouseMotionListener
  722. {
  723. protected transient int offset;
  724. protected transient int currentMouseX, currentMouseY;
  725. private transient int direction = +1;
  726. public void mouseReleased(MouseEvent e)
  727. {
  728. if (SwingUtilities.isRightMouseButton(e) ||
  729. (!getSupportsAbsolutePositioning() &&
  730. SwingUtilities.isMiddleMouseButton(e)))
  731. return;
  732. if(!scrollbar.isEnabled())
  733. return;
  734. Rectangle r = getTrackBounds();
  735. scrollbar.repaint(r.x, r.y, r.width, r.height);
  736. trackHighlight = NO_HIGHLIGHT;
  737. isDragging = false;
  738. offset = 0;
  739. scrollTimer.stop();
  740. scrollbar.setValueIsAdjusting(false);
  741. }
  742. /**
  743. * If the mouse is pressed above the "thumb" component
  744. * then reduce the scrollbars value by one page ("page up"),
  745. * otherwise increase it by one page. If there is no
  746. * thumb then page up if the mouse is in the upper half
  747. * of the track.
  748. */
  749. public void mousePressed(MouseEvent e)
  750. {
  751. if (SwingUtilities.isRightMouseButton(e) ||
  752. (!getSupportsAbsolutePositioning() &&
  753. SwingUtilities.isMiddleMouseButton(e)))
  754. return;
  755. if(!scrollbar.isEnabled())
  756. return;
  757. if (!scrollbar.hasFocus() && scrollbar.isRequestFocusEnabled()) {
  758. scrollbar.requestFocus();
  759. }
  760. scrollbar.setValueIsAdjusting(true);
  761. currentMouseX = e.getX();
  762. currentMouseY = e.getY();
  763. // Clicked in the Thumb area?
  764. if(getThumbBounds().contains(currentMouseX, currentMouseY)) {
  765. switch (scrollbar.getOrientation()) {
  766. case JScrollBar.VERTICAL:
  767. offset = currentMouseY - getThumbBounds().y;
  768. break;
  769. case JScrollBar.HORIZONTAL:
  770. offset = currentMouseX - getThumbBounds().x;
  771. break;
  772. }
  773. isDragging = true;
  774. return;
  775. }
  776. else if (getSupportsAbsolutePositioning() &&
  777. SwingUtilities.isMiddleMouseButton(e)) {
  778. switch (scrollbar.getOrientation()) {
  779. case JScrollBar.VERTICAL:
  780. offset = getThumbBounds().height / 2;
  781. break;
  782. case JScrollBar.HORIZONTAL:
  783. offset = getThumbBounds().width / 2;
  784. break;
  785. }
  786. isDragging = true;
  787. setValueFrom(e);
  788. return;
  789. }
  790. isDragging = false;
  791. Dimension sbSize = scrollbar.getSize();
  792. direction = +1;
  793. switch (scrollbar.getOrientation()) {
  794. case JScrollBar.VERTICAL:
  795. if (getThumbBounds().isEmpty()) {
  796. int scrollbarCenter = sbSize.height / 2;
  797. direction = (currentMouseY < scrollbarCenter) ? -1 : +1;
  798. } else {
  799. int thumbY = getThumbBounds().y;
  800. direction = (currentMouseY < thumbY) ? -1 : +1;
  801. }
  802. break;
  803. case JScrollBar.HORIZONTAL:
  804. if (getThumbBounds().isEmpty()) {
  805. int scrollbarCenter = sbSize.width / 2;
  806. direction = (currentMouseX < scrollbarCenter) ? -1 : +1;
  807. } else {
  808. int thumbX = getThumbBounds().x;
  809. direction = (currentMouseX < thumbX) ? -1 : +1;
  810. }
  811. if (!scrollbar.getComponentOrientation().isLeftToRight()) {
  812. direction = -direction;
  813. }
  814. break;
  815. }
  816. scrollByBlock(direction);
  817. scrollTimer.stop();
  818. scrollListener.setDirection(direction);
  819. scrollListener.setScrollByBlock(true);
  820. startScrollTimerIfNecessary();
  821. }
  822. /**
  823. * Set the models value to the position of the thumb's top of Vertical
  824. * scrollbar, or the left/right of Horizontal scrollbar in
  825. * left-to-right/right-to-left scrollbar relative to the origin of the
  826. * track.
  827. */
  828. public void mouseDragged(MouseEvent e) {
  829. if (SwingUtilities.isRightMouseButton(e) ||
  830. (!getSupportsAbsolutePositioning() &&
  831. SwingUtilities.isMiddleMouseButton(e)))
  832. return;
  833. if(!scrollbar.isEnabled() || getThumbBounds().isEmpty()) {
  834. return;
  835. }
  836. if (isDragging) {
  837. setValueFrom(e);
  838. } else {
  839. currentMouseX = e.getX();
  840. currentMouseY = e.getY();
  841. startScrollTimerIfNecessary();
  842. }
  843. }
  844. private void setValueFrom(MouseEvent e) {
  845. BoundedRangeModel model = scrollbar.getModel();
  846. Rectangle thumbR = getThumbBounds();
  847. float trackLength;
  848. int thumbMin, thumbMax, thumbPos;
  849. if (scrollbar.getOrientation() == JScrollBar.VERTICAL) {
  850. thumbMin = decrButton.getY() + decrButton.getHeight();
  851. thumbMax = incrButton.getY() - thumbR.height;
  852. thumbPos = Math.min(thumbMax, Math.max(thumbMin, (e.getY() - offset)));
  853. setThumbBounds(thumbR.x, thumbPos, thumbR.width, thumbR.height);
  854. trackLength = getTrackBounds().height;
  855. }
  856. else {
  857. if (scrollbar.getComponentOrientation().isLeftToRight()) {
  858. thumbMin = decrButton.getX() + decrButton.getWidth();
  859. thumbMax = incrButton.getX() - thumbR.width;
  860. } else {
  861. thumbMin = incrButton.getX() + incrButton.getWidth();
  862. thumbMax = decrButton.getX() - thumbR.width;
  863. }
  864. thumbPos = Math.min(thumbMax, Math.max(thumbMin, (e.getX() - offset)));
  865. setThumbBounds(thumbPos, thumbR.y, thumbR.width, thumbR.height);
  866. trackLength = getTrackBounds().width;
  867. }
  868. /* Set the scrollbars value. If the thumb has reached the end of
  869. * the scrollbar, then just set the value to its maximum. Otherwise
  870. * compute the value as accurately as possible.
  871. */
  872. if (thumbPos == thumbMax) {
  873. if (scrollbar.getOrientation() == JScrollBar.VERTICAL ||
  874. scrollbar.getComponentOrientation().isLeftToRight()) {
  875. scrollbar.setValue(model.getMaximum() - model.getExtent());
  876. } else {
  877. scrollbar.setValue(model.getMinimum());
  878. }
  879. }
  880. else {
  881. float valueMax = model.getMaximum() - model.getExtent();
  882. float valueRange = valueMax - model.getMinimum();
  883. float thumbValue = thumbPos - thumbMin;
  884. float thumbRange = thumbMax - thumbMin;
  885. int value;
  886. if (scrollbar.getOrientation() == JScrollBar.VERTICAL ||
  887. scrollbar.getComponentOrientation().isLeftToRight()) {
  888. value = (int)(0.5 + ((thumbValue / thumbRange) * valueRange));
  889. } else {
  890. value = (int)(0.5 + (((thumbMax - thumbPos) / thumbRange) * valueRange));
  891. }
  892. scrollbar.setValue(value + model.getMinimum());
  893. }
  894. }
  895. private void startScrollTimerIfNecessary() {
  896. if (scrollTimer.isRunning()) {
  897. return;
  898. }
  899. switch (scrollbar.getOrientation()) {
  900. case JScrollBar.VERTICAL:
  901. if (direction >0) {
  902. if (getThumbBounds().y + getThumbBounds().height <
  903. trackListener.currentMouseY) {
  904. scrollTimer.start();
  905. }
  906. } else if (getThumbBounds().y >
  907. trackListener.currentMouseY) {
  908. scrollTimer.start();
  909. }
  910. break;
  911. case JScrollBar.HORIZONTAL:
  912. if (direction >0) {
  913. if (getThumbBounds().x + getThumbBounds().width <
  914. trackListener.currentMouseX) {
  915. scrollTimer.start();
  916. }
  917. } else if (getThumbBounds().x >
  918. trackListener.currentMouseX) {
  919. scrollTimer.start();
  920. }
  921. break;
  922. }
  923. }
  924. public void mouseMoved(MouseEvent e) {
  925. }
  926. }
  927. /**
  928. * Listener for cursor keys.
  929. */
  930. protected class ArrowButtonListener extends MouseAdapter
  931. {
  932. // Because we are handling both mousePressed and Actions
  933. // we need to make sure we don't fire under both conditions.
  934. // (keyfocus on scrollbars causes action without mousePress
  935. boolean handledEvent;
  936. public void mousePressed(MouseEvent e) {
  937. if(!scrollbar.isEnabled()) { return; }
  938. // not an unmodified left mouse button
  939. //if(e.getModifiers() != InputEvent.BUTTON1_MASK) {return; }
  940. if( ! SwingUtilities.isLeftMouseButton(e)) { return; }
  941. int direction = (e.getSource() == incrButton) ? 1 : -1;
  942. scrollByUnit(direction);
  943. scrollTimer.stop();
  944. scrollListener.setDirection(direction);
  945. scrollListener.setScrollByBlock(false);
  946. scrollTimer.start();
  947. handledEvent = true;
  948. if (!scrollbar.hasFocus() && scrollbar.isRequestFocusEnabled()) {
  949. scrollbar.requestFocus();
  950. }
  951. }
  952. public void mouseReleased(MouseEvent e) {
  953. scrollTimer.stop();
  954. handledEvent = false;
  955. scrollbar.setValueIsAdjusting(false);
  956. }
  957. }
  958. /**
  959. * Listener for scrolling events initiated in the
  960. * <code>ScrollPane</code>.
  961. */
  962. protected class ScrollListener implements ActionListener
  963. {
  964. int direction = +1;
  965. boolean useBlockIncrement;
  966. public ScrollListener() {
  967. direction = +1;
  968. useBlockIncrement = false;
  969. }
  970. public ScrollListener(int dir, boolean block) {
  971. direction = dir;
  972. useBlockIncrement = block;
  973. }
  974. public void setDirection(int direction) { this.direction = direction; }
  975. public void setScrollByBlock(boolean block) { this.useBlockIncrement = block; }
  976. public void actionPerformed(ActionEvent e) {
  977. if(useBlockIncrement) {
  978. scrollByBlock(direction);
  979. // Stop scrolling if the thumb catches up with the mouse
  980. if(scrollbar.getOrientation() == JScrollBar.VERTICAL) {
  981. if(direction > 0) {
  982. if(getThumbBounds().y + getThumbBounds().height
  983. >= trackListener.currentMouseY)
  984. ((Timer)e.getSource()).stop();
  985. } else if(getThumbBounds().y <= trackListener.currentMouseY) {
  986. ((Timer)e.getSource()).stop();
  987. }
  988. } else {
  989. if(direction > 0) {
  990. if(getThumbBounds().x + getThumbBounds().width
  991. >= trackListener.currentMouseX)
  992. ((Timer)e.getSource()).stop();
  993. } else if(getThumbBounds().x <= trackListener.currentMouseX) {
  994. ((Timer)e.getSource()).stop();
  995. }
  996. }
  997. } else {
  998. scrollByUnit(direction);
  999. }
  1000. if(direction > 0
  1001. && scrollbar.getValue()+scrollbar.getVisibleAmount()
  1002. >= scrollbar.getMaximum())
  1003. ((Timer)e.getSource()).stop();
  1004. else if(direction < 0
  1005. && scrollbar.getValue() <= scrollbar.getMinimum())
  1006. ((Timer)e.getSource()).stop();
  1007. }
  1008. }
  1009. public class PropertyChangeHandler implements PropertyChangeListener
  1010. {
  1011. public void propertyChange(PropertyChangeEvent e) {
  1012. String propertyName = e.getPropertyName();
  1013. if ("model".equals(propertyName)) {
  1014. BoundedRangeModel oldModel = (BoundedRangeModel)e.getOldValue();
  1015. BoundedRangeModel newModel = (BoundedRangeModel)e.getNewValue();
  1016. oldModel.removeChangeListener(modelListener);
  1017. newModel.addChangeListener(modelListener);
  1018. scrollbar.repaint();
  1019. scrollbar.revalidate();
  1020. } else if ("orientation".equals(propertyName)) {
  1021. Integer orient = (Integer)e.getNewValue();
  1022. if (scrollbar.getComponentOrientation().isLeftToRight()) {
  1023. if (incrButton instanceof BasicArrowButton) {
  1024. ((BasicArrowButton)incrButton).setDirection(orient.intValue() == HORIZONTAL?
  1025. EAST : SOUTH);
  1026. }
  1027. if (decrButton instanceof BasicArrowButton) {
  1028. ((BasicArrowButton)decrButton).setDirection(orient.intValue() == HORIZONTAL?
  1029. WEST : NORTH);
  1030. }
  1031. } else {
  1032. if (incrButton instanceof BasicArrowButton) {
  1033. ((BasicArrowButton)incrButton).setDirection(orient.intValue() == HORIZONTAL?
  1034. WEST : SOUTH);
  1035. }
  1036. if (decrButton instanceof BasicArrowButton) {
  1037. ((BasicArrowButton)decrButton).setDirection(orient.intValue() == HORIZONTAL?
  1038. EAST : NORTH);
  1039. }
  1040. }
  1041. } else if ("componentOrientation".equals(propertyName)) {
  1042. ComponentOrientation co = scrollbar.getComponentOrientation();
  1043. incrButton.setComponentOrientation(co);
  1044. decrButton.setComponentOrientation(co);
  1045. if (scrollbar.getOrientation() == JScrollBar.HORIZONTAL) {
  1046. if (co.isLeftToRight()) {
  1047. if (incrButton instanceof BasicArrowButton) {
  1048. ((BasicArrowButton)incrButton).setDirection(EAST);
  1049. }
  1050. if (decrButton instanceof BasicArrowButton) {
  1051. ((BasicArrowButton)decrButton).setDirection(WEST);
  1052. }
  1053. } else {
  1054. if (incrButton instanceof BasicArrowButton) {
  1055. ((BasicArrowButton)incrButton).setDirection(WEST);
  1056. }
  1057. if (decrButton instanceof BasicArrowButton) {
  1058. ((BasicArrowButton)decrButton).setDirection(EAST);
  1059. }
  1060. }
  1061. }
  1062. InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED);
  1063. SwingUtilities.replaceUIInputMap(scrollbar, JComponent.WHEN_FOCUSED, inputMap);
  1064. }
  1065. }
  1066. }
  1067. /**
  1068. * Used for scrolling the scrollbar.
  1069. */
  1070. private static class SharedActionScroller extends AbstractAction {
  1071. private int dir;
  1072. private boolean block;
  1073. SharedActionScroller(int dir, boolean block) {
  1074. this.dir = dir;
  1075. this.block = block;
  1076. }
  1077. public void actionPerformed(ActionEvent e) {
  1078. JScrollBar scrollBar = (JScrollBar)e.getSource();
  1079. if (dir == NEGATIVE_SCROLL || dir == POSITIVE_SCROLL) {
  1080. int amount;
  1081. // Don't use the BasicScrollBarUI.scrollByXXX methods as we
  1082. // don't want to use an invokeLater to reset the trackHighlight
  1083. // via an invokeLater
  1084. if (block) {
  1085. if (dir == NEGATIVE_SCROLL) {
  1086. amount = -1 * scrollBar.getBlockIncrement(-1);
  1087. }
  1088. else {
  1089. amount = scrollBar.getBlockIncrement(1);
  1090. }
  1091. }
  1092. else {
  1093. if (dir == NEGATIVE_SCROLL) {
  1094. amount = -1 * scrollBar.getUnitIncrement(-1);
  1095. }
  1096. else {
  1097. amount = scrollBar.getUnitIncrement(1);
  1098. }
  1099. }
  1100. scrollBar.setValue(scrollBar.getValue() + amount);
  1101. }
  1102. else if (dir == MIN_SCROLL) {
  1103. scrollBar.setValue(scrollBar.getMinimum());
  1104. }
  1105. else if (dir == MAX_SCROLL) {
  1106. scrollBar.setValue(scrollBar.getMaximum());
  1107. }
  1108. }
  1109. }
  1110. }