[6380] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
[1180] | 2 | package org.openstreetmap.josm.gui;
|
---|
| 3 |
|
---|
| 4 | import java.awt.BorderLayout;
|
---|
| 5 | import java.awt.Dimension;
|
---|
| 6 | import java.awt.Point;
|
---|
| 7 | import java.awt.Rectangle;
|
---|
[2391] | 8 | import java.awt.event.ComponentAdapter;
|
---|
| 9 | import java.awt.event.ComponentEvent;
|
---|
[1180] | 10 | import java.awt.event.MouseAdapter;
|
---|
| 11 | import java.awt.event.MouseEvent;
|
---|
[2391] | 12 | import java.util.ArrayList;
|
---|
| 13 | import java.util.List;
|
---|
| 14 |
|
---|
[1180] | 15 | import javax.swing.JButton;
|
---|
| 16 | import javax.swing.JComponent;
|
---|
| 17 | import javax.swing.JPanel;
|
---|
| 18 | import javax.swing.JViewport;
|
---|
| 19 | import javax.swing.Timer;
|
---|
| 20 |
|
---|
| 21 | import org.openstreetmap.josm.tools.ImageProvider;
|
---|
| 22 |
|
---|
[12073] | 23 | /**
|
---|
| 24 | * A viewport with UP and DOWN arrow buttons, so that the user can make the
|
---|
[1180] | 25 | * content scroll.
|
---|
[12073] | 26 | *
|
---|
| 27 | * This should be used for long, vertical toolbars.
|
---|
[1180] | 28 | */
|
---|
| 29 | public class ScrollViewport extends JPanel {
|
---|
| 30 |
|
---|
| 31 | private static final int NO_SCROLL = 0;
|
---|
| 32 |
|
---|
| 33 | public static final int UP_DIRECTION = 1;
|
---|
| 34 | public static final int DOWN_DIRECTION = 2;
|
---|
| 35 | public static final int LEFT_DIRECTION = 4;
|
---|
| 36 | public static final int RIGHT_DIRECTION = 8;
|
---|
[12073] | 37 | /**
|
---|
| 38 | * Allow vertical scrolling
|
---|
| 39 | */
|
---|
[1180] | 40 | public static final int VERTICAL_DIRECTION = UP_DIRECTION | DOWN_DIRECTION;
|
---|
[12073] | 41 |
|
---|
| 42 | /**
|
---|
| 43 | * Allow horizontal scrolling
|
---|
| 44 | */
|
---|
[1180] | 45 | public static final int HORIZONTAL_DIRECTION = LEFT_DIRECTION | RIGHT_DIRECTION;
|
---|
[12073] | 46 |
|
---|
| 47 | /**
|
---|
| 48 | * Allow scrolling in both directions
|
---|
| 49 | */
|
---|
[1180] | 50 | public static final int ALL_DIRECTION = HORIZONTAL_DIRECTION | VERTICAL_DIRECTION;
|
---|
| 51 |
|
---|
| 52 | private class ScrollViewPortMouseListener extends MouseAdapter {
|
---|
[9078] | 53 | private final int direction;
|
---|
[1180] | 54 |
|
---|
[8836] | 55 | ScrollViewPortMouseListener(int direction) {
|
---|
[1180] | 56 | this.direction = direction;
|
---|
| 57 | }
|
---|
| 58 |
|
---|
[8836] | 59 | @Override
|
---|
| 60 | public void mouseExited(MouseEvent arg0) {
|
---|
[1180] | 61 | ScrollViewport.this.scrollDirection = NO_SCROLL;
|
---|
| 62 | timer.stop();
|
---|
| 63 | }
|
---|
| 64 |
|
---|
[8836] | 65 | @Override
|
---|
| 66 | public void mouseReleased(MouseEvent arg0) {
|
---|
[1180] | 67 | ScrollViewport.this.scrollDirection = NO_SCROLL;
|
---|
| 68 | timer.stop();
|
---|
| 69 | }
|
---|
| 70 |
|
---|
| 71 | @Override public void mousePressed(MouseEvent arg0) {
|
---|
| 72 | ScrollViewport.this.scrollDirection = direction;
|
---|
| 73 | scroll();
|
---|
| 74 | timer.restart();
|
---|
| 75 | }
|
---|
| 76 |
|
---|
| 77 | }
|
---|
| 78 |
|
---|
[9078] | 79 | private final JViewport vp = new JViewport();
|
---|
[8840] | 80 | private JComponent component;
|
---|
[2512] | 81 |
|
---|
[9078] | 82 | private final List<JButton> buttons = new ArrayList<>();
|
---|
[1180] | 83 |
|
---|
[10634] | 84 | private final Timer timer = new Timer(100, evt -> scroll());
|
---|
[1180] | 85 |
|
---|
| 86 | private int scrollDirection = NO_SCROLL;
|
---|
| 87 |
|
---|
[12073] | 88 | private final int allowedScrollDirections;
|
---|
| 89 |
|
---|
| 90 | private final ComponentAdapter refreshButtonsOnResize = new ComponentAdapter() {
|
---|
| 91 | @Override
|
---|
| 92 | public void componentResized(ComponentEvent e) {
|
---|
| 93 | showOrHideButtons();
|
---|
| 94 | }
|
---|
| 95 | };
|
---|
| 96 |
|
---|
| 97 | /**
|
---|
| 98 | * Create a new scroll viewport
|
---|
| 99 | * @param c The component to display as content.
|
---|
| 100 | * @param direction The direction to scroll.
|
---|
| 101 | * Should be one of {@link #VERTICAL_DIRECTION}, {@link #HORIZONTAL_DIRECTION}, {@link #ALL_DIRECTION}
|
---|
| 102 | */
|
---|
[1180] | 103 | public ScrollViewport(JComponent c, int direction) {
|
---|
| 104 | this(direction);
|
---|
| 105 | add(c);
|
---|
| 106 | }
|
---|
| 107 |
|
---|
[12073] | 108 | /**
|
---|
| 109 | * Create a new scroll viewport
|
---|
| 110 | * @param direction The direction to scroll.
|
---|
| 111 | * Should be one of {@link #VERTICAL_DIRECTION}, {@link #HORIZONTAL_DIRECTION}, {@link #ALL_DIRECTION}
|
---|
| 112 | */
|
---|
[1180] | 113 | public ScrollViewport(int direction) {
|
---|
[12073] | 114 | super(new BorderLayout());
|
---|
| 115 | this.allowedScrollDirections = direction;
|
---|
[2512] | 116 |
|
---|
[1180] | 117 | // UP
|
---|
[10244] | 118 | if ((direction & UP_DIRECTION) != 0) {
|
---|
[12073] | 119 | addScrollButton(UP_DIRECTION, "svpUp", BorderLayout.NORTH);
|
---|
[1180] | 120 | }
|
---|
| 121 |
|
---|
| 122 | // DOWN
|
---|
[10244] | 123 | if ((direction & DOWN_DIRECTION) != 0) {
|
---|
[12073] | 124 | addScrollButton(DOWN_DIRECTION, "svpDown", BorderLayout.SOUTH);
|
---|
[1180] | 125 | }
|
---|
| 126 |
|
---|
| 127 | // LEFT
|
---|
[10244] | 128 | if ((direction & LEFT_DIRECTION) != 0) {
|
---|
[12073] | 129 | addScrollButton(LEFT_DIRECTION, "svpLeft", BorderLayout.WEST);
|
---|
[1180] | 130 | }
|
---|
| 131 |
|
---|
| 132 | // RIGHT
|
---|
[10244] | 133 | if ((direction & RIGHT_DIRECTION) != 0) {
|
---|
[12073] | 134 | addScrollButton(RIGHT_DIRECTION, "svpRight", BorderLayout.EAST);
|
---|
[1180] | 135 | }
|
---|
| 136 |
|
---|
| 137 | add(vp, BorderLayout.CENTER);
|
---|
| 138 |
|
---|
[12073] | 139 | this.addComponentListener(refreshButtonsOnResize);
|
---|
[2391] | 140 |
|
---|
| 141 | showOrHideButtons();
|
---|
[2512] | 142 |
|
---|
[12079] | 143 | if ((direction & VERTICAL_DIRECTION) != 0) {
|
---|
| 144 | addMouseWheelListener(e -> scroll(0, e.getUnitsToScroll() * 5));
|
---|
| 145 | } else if ((direction & HORIZONTAL_DIRECTION) != 0) {
|
---|
| 146 | addMouseWheelListener(e -> scroll(e.getUnitsToScroll() * 5, 0));
|
---|
| 147 | }
|
---|
| 148 |
|
---|
[1180] | 149 | timer.setRepeats(true);
|
---|
| 150 | timer.setInitialDelay(400);
|
---|
| 151 | }
|
---|
| 152 |
|
---|
[12073] | 153 | private void addScrollButton(int direction, String icon, String borderLayoutPosition) {
|
---|
| 154 | JButton button = new JButton();
|
---|
| 155 | button.addMouseListener(new ScrollViewPortMouseListener(direction));
|
---|
| 156 | button.setPreferredSize(new Dimension(10, 10));
|
---|
| 157 | button.setIcon(ImageProvider.get(icon));
|
---|
| 158 | add(button, borderLayoutPosition);
|
---|
| 159 | buttons.add(button);
|
---|
| 160 | }
|
---|
| 161 |
|
---|
| 162 | /**
|
---|
| 163 | * Scrolls in the currently selected scroll direction.
|
---|
| 164 | */
|
---|
[1180] | 165 | public synchronized void scroll() {
|
---|
| 166 | int direction = scrollDirection;
|
---|
| 167 |
|
---|
[2676] | 168 | if (component == null || direction == NO_SCROLL)
|
---|
[1180] | 169 | return;
|
---|
| 170 |
|
---|
| 171 | Rectangle viewRect = vp.getViewRect();
|
---|
| 172 |
|
---|
[2391] | 173 | int deltaX = 0;
|
---|
| 174 | int deltaY = 0;
|
---|
[1180] | 175 |
|
---|
| 176 | if (direction < LEFT_DIRECTION) {
|
---|
[2391] | 177 | deltaY = viewRect.height * 2 / 7;
|
---|
[1180] | 178 | } else {
|
---|
[2391] | 179 | deltaX = viewRect.width * 2 / 7;
|
---|
[1180] | 180 | }
|
---|
| 181 |
|
---|
| 182 | switch (direction) {
|
---|
[2676] | 183 | case UP_DIRECTION :
|
---|
| 184 | deltaY *= -1;
|
---|
| 185 | break;
|
---|
| 186 | case LEFT_DIRECTION :
|
---|
| 187 | deltaX *= -1;
|
---|
| 188 | break;
|
---|
[10217] | 189 | default: // Do nothing
|
---|
[1180] | 190 | }
|
---|
| 191 |
|
---|
[2391] | 192 | scroll(deltaX, deltaY);
|
---|
| 193 | }
|
---|
[8510] | 194 |
|
---|
[12073] | 195 | /**
|
---|
| 196 | * Scrolls by the given offset
|
---|
| 197 | * @param deltaX offset x
|
---|
| 198 | * @param deltaY offset y
|
---|
| 199 | */
|
---|
[2391] | 200 | public synchronized void scroll(int deltaX, int deltaY) {
|
---|
[2676] | 201 | if (component == null)
|
---|
[2391] | 202 | return;
|
---|
| 203 | Dimension compSize = component.getSize();
|
---|
| 204 | Rectangle viewRect = vp.getViewRect();
|
---|
| 205 |
|
---|
| 206 | int newX = viewRect.x + deltaX;
|
---|
| 207 | int newY = viewRect.y + deltaY;
|
---|
| 208 |
|
---|
| 209 | if (newY < 0) {
|
---|
| 210 | newY = 0;
|
---|
| 211 | }
|
---|
| 212 | if (newY > compSize.height - viewRect.height) {
|
---|
| 213 | newY = compSize.height - viewRect.height;
|
---|
| 214 | }
|
---|
| 215 | if (newX < 0) {
|
---|
| 216 | newX = 0;
|
---|
| 217 | }
|
---|
| 218 | if (newX > compSize.width - viewRect.width) {
|
---|
| 219 | newX = compSize.width - viewRect.width;
|
---|
| 220 | }
|
---|
| 221 |
|
---|
[1180] | 222 | vp.setViewPosition(new Point(newX, newY));
|
---|
| 223 | }
|
---|
| 224 |
|
---|
[2391] | 225 | /**
|
---|
| 226 | * Update the visibility of the buttons
|
---|
| 227 | * Only show them if the Viewport is too small for the content.
|
---|
| 228 | */
|
---|
| 229 | public void showOrHideButtons() {
|
---|
[12073] | 230 | boolean needButtons = false;
|
---|
| 231 | if ((allowedScrollDirections & VERTICAL_DIRECTION) != 0) {
|
---|
| 232 | needButtons |= getViewSize().height > getViewRect().height;
|
---|
| 233 | }
|
---|
| 234 | if ((allowedScrollDirections & HORIZONTAL_DIRECTION) != 0) {
|
---|
| 235 | needButtons |= getViewSize().width > getViewRect().width;
|
---|
| 236 | }
|
---|
[2391] | 237 | for (JButton b : buttons) {
|
---|
| 238 | b.setVisible(needButtons);
|
---|
| 239 | }
|
---|
| 240 | }
|
---|
[2512] | 241 |
|
---|
[1180] | 242 | public Rectangle getViewRect() {
|
---|
| 243 | return vp.getViewRect();
|
---|
| 244 | }
|
---|
| 245 |
|
---|
| 246 | public Dimension getViewSize() {
|
---|
| 247 | return vp.getViewSize();
|
---|
| 248 | }
|
---|
| 249 |
|
---|
| 250 | public Point getViewPosition() {
|
---|
| 251 | return vp.getViewPosition();
|
---|
| 252 | }
|
---|
| 253 |
|
---|
[12073] | 254 | @Override
|
---|
| 255 | public Dimension getPreferredSize() {
|
---|
[12079] | 256 | if (component == null) {
|
---|
| 257 | return vp.getPreferredSize();
|
---|
| 258 | } else {
|
---|
| 259 | return component.getPreferredSize();
|
---|
| 260 | }
|
---|
[12073] | 261 | }
|
---|
| 262 |
|
---|
| 263 | @Override
|
---|
| 264 | public Dimension getMinimumSize() {
|
---|
[12079] | 265 | if (component == null) {
|
---|
| 266 | return vp.getMinimumSize();
|
---|
| 267 | } else {
|
---|
| 268 | Dimension minSize = component.getMinimumSize();
|
---|
| 269 | if ((allowedScrollDirections & HORIZONTAL_DIRECTION) != 0) {
|
---|
| 270 | minSize = new Dimension(20, minSize.height);
|
---|
| 271 | }
|
---|
| 272 | if ((allowedScrollDirections & VERTICAL_DIRECTION) != 0) {
|
---|
| 273 | minSize = new Dimension(minSize.width, 20);
|
---|
| 274 | }
|
---|
| 275 | return minSize;
|
---|
| 276 | }
|
---|
[12073] | 277 | }
|
---|
| 278 |
|
---|
| 279 | /**
|
---|
| 280 | * Sets the component to be used as content.
|
---|
| 281 | * @param c The component
|
---|
| 282 | */
|
---|
[1180] | 283 | public void add(JComponent c) {
|
---|
| 284 | vp.removeAll();
|
---|
[12073] | 285 | if (this.component != null) {
|
---|
| 286 | this.component.removeComponentListener(refreshButtonsOnResize);
|
---|
| 287 | }
|
---|
[1180] | 288 | this.component = c;
|
---|
[12073] | 289 | c.addComponentListener(refreshButtonsOnResize);
|
---|
[1180] | 290 | vp.add(c);
|
---|
| 291 | }
|
---|
| 292 | }
|
---|