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