source: josm/trunk/src/org/openstreetmap/josm/gui/ScrollViewport.java@ 12103

Last change on this file since 12103 was 12079, checked in by michael2402, 7 years ago

ScrollViewport: Fix layout computation, natively support mouse wheel

The layout size width/height is set to the one of the inner component if scrolling in that direction is not enabled.
The ScrollViewport now supports the mouse wheel in both horizontal and vertical versions.

  • Property svn:eol-style set to native
File size: 8.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui;
3
4import java.awt.BorderLayout;
5import java.awt.Dimension;
6import java.awt.Point;
7import java.awt.Rectangle;
8import java.awt.event.ComponentAdapter;
9import java.awt.event.ComponentEvent;
10import java.awt.event.MouseAdapter;
11import java.awt.event.MouseEvent;
12import java.util.ArrayList;
13import java.util.List;
14
15import javax.swing.JButton;
16import javax.swing.JComponent;
17import javax.swing.JPanel;
18import javax.swing.JViewport;
19import javax.swing.Timer;
20
21import org.openstreetmap.josm.tools.ImageProvider;
22
23/**
24 * A viewport with UP and DOWN arrow buttons, so that the user can make the
25 * content scroll.
26 *
27 * This should be used for long, vertical toolbars.
28 */
29public 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;
37 /**
38 * Allow vertical scrolling
39 */
40 public static final int VERTICAL_DIRECTION = UP_DIRECTION | DOWN_DIRECTION;
41
42 /**
43 * Allow horizontal scrolling
44 */
45 public static final int HORIZONTAL_DIRECTION = LEFT_DIRECTION | RIGHT_DIRECTION;
46
47 /**
48 * Allow scrolling in both directions
49 */
50 public static final int ALL_DIRECTION = HORIZONTAL_DIRECTION | VERTICAL_DIRECTION;
51
52 private class ScrollViewPortMouseListener extends MouseAdapter {
53 private final int direction;
54
55 ScrollViewPortMouseListener(int direction) {
56 this.direction = direction;
57 }
58
59 @Override
60 public void mouseExited(MouseEvent arg0) {
61 ScrollViewport.this.scrollDirection = NO_SCROLL;
62 timer.stop();
63 }
64
65 @Override
66 public void mouseReleased(MouseEvent arg0) {
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
79 private final JViewport vp = new JViewport();
80 private JComponent component;
81
82 private final List<JButton> buttons = new ArrayList<>();
83
84 private final Timer timer = new Timer(100, evt -> scroll());
85
86 private int scrollDirection = NO_SCROLL;
87
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 */
103 public ScrollViewport(JComponent c, int direction) {
104 this(direction);
105 add(c);
106 }
107
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 */
113 public ScrollViewport(int direction) {
114 super(new BorderLayout());
115 this.allowedScrollDirections = direction;
116
117 // UP
118 if ((direction & UP_DIRECTION) != 0) {
119 addScrollButton(UP_DIRECTION, "svpUp", BorderLayout.NORTH);
120 }
121
122 // DOWN
123 if ((direction & DOWN_DIRECTION) != 0) {
124 addScrollButton(DOWN_DIRECTION, "svpDown", BorderLayout.SOUTH);
125 }
126
127 // LEFT
128 if ((direction & LEFT_DIRECTION) != 0) {
129 addScrollButton(LEFT_DIRECTION, "svpLeft", BorderLayout.WEST);
130 }
131
132 // RIGHT
133 if ((direction & RIGHT_DIRECTION) != 0) {
134 addScrollButton(RIGHT_DIRECTION, "svpRight", BorderLayout.EAST);
135 }
136
137 add(vp, BorderLayout.CENTER);
138
139 this.addComponentListener(refreshButtonsOnResize);
140
141 showOrHideButtons();
142
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
149 timer.setRepeats(true);
150 timer.setInitialDelay(400);
151 }
152
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 */
165 public synchronized void scroll() {
166 int direction = scrollDirection;
167
168 if (component == null || direction == NO_SCROLL)
169 return;
170
171 Rectangle viewRect = vp.getViewRect();
172
173 int deltaX = 0;
174 int deltaY = 0;
175
176 if (direction < LEFT_DIRECTION) {
177 deltaY = viewRect.height * 2 / 7;
178 } else {
179 deltaX = viewRect.width * 2 / 7;
180 }
181
182 switch (direction) {
183 case UP_DIRECTION :
184 deltaY *= -1;
185 break;
186 case LEFT_DIRECTION :
187 deltaX *= -1;
188 break;
189 default: // Do nothing
190 }
191
192 scroll(deltaX, deltaY);
193 }
194
195 /**
196 * Scrolls by the given offset
197 * @param deltaX offset x
198 * @param deltaY offset y
199 */
200 public synchronized void scroll(int deltaX, int deltaY) {
201 if (component == null)
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
222 vp.setViewPosition(new Point(newX, newY));
223 }
224
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() {
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 }
237 for (JButton b : buttons) {
238 b.setVisible(needButtons);
239 }
240 }
241
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
254 @Override
255 public Dimension getPreferredSize() {
256 if (component == null) {
257 return vp.getPreferredSize();
258 } else {
259 return component.getPreferredSize();
260 }
261 }
262
263 @Override
264 public Dimension getMinimumSize() {
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 }
277 }
278
279 /**
280 * Sets the component to be used as content.
281 * @param c The component
282 */
283 public void add(JComponent c) {
284 vp.removeAll();
285 if (this.component != null) {
286 this.component.removeComponentListener(refreshButtonsOnResize);
287 }
288 this.component = c;
289 c.addComponentListener(refreshButtonsOnResize);
290 vp.add(c);
291 }
292}
Note: See TracBrowser for help on using the repository browser.