001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui;
003
004import java.awt.BorderLayout;
005import java.awt.Dimension;
006import java.awt.Point;
007import java.awt.Rectangle;
008import java.awt.event.ComponentAdapter;
009import java.awt.event.ComponentEvent;
010import java.awt.event.MouseAdapter;
011import java.awt.event.MouseEvent;
012import java.util.ArrayList;
013import java.util.List;
014
015import javax.swing.JButton;
016import javax.swing.JComponent;
017import javax.swing.JPanel;
018import javax.swing.JViewport;
019import javax.swing.Timer;
020
021import org.openstreetmap.josm.tools.ImageProvider;
022
023/** A viewport with UP and DOWN arrow buttons, so that the user can make the
024 * content scroll.
025 */
026public class ScrollViewport extends JPanel {
027
028    private static final int NO_SCROLL = 0;
029
030    public static final int UP_DIRECTION = 1;
031    public static final int DOWN_DIRECTION = 2;
032    public static final int LEFT_DIRECTION = 4;
033    public static final int RIGHT_DIRECTION = 8;
034    public static final int VERTICAL_DIRECTION = UP_DIRECTION | DOWN_DIRECTION;
035    public static final int HORIZONTAL_DIRECTION = LEFT_DIRECTION | RIGHT_DIRECTION;
036    public static final int ALL_DIRECTION = HORIZONTAL_DIRECTION | VERTICAL_DIRECTION;
037
038    private class ScrollViewPortMouseListener extends MouseAdapter {
039        private final int direction;
040
041        ScrollViewPortMouseListener(int direction) {
042            this.direction = direction;
043        }
044
045        @Override
046        public void mouseExited(MouseEvent arg0) {
047            ScrollViewport.this.scrollDirection = NO_SCROLL;
048            timer.stop();
049        }
050
051        @Override
052        public void mouseReleased(MouseEvent arg0) {
053            ScrollViewport.this.scrollDirection = NO_SCROLL;
054            timer.stop();
055        }
056
057        @Override public void mousePressed(MouseEvent arg0) {
058            ScrollViewport.this.scrollDirection = direction;
059            scroll();
060            timer.restart();
061        }
062
063    }
064
065    private final JViewport vp = new JViewport();
066    private JComponent component;
067
068    private final List<JButton> buttons = new ArrayList<>();
069
070    private final Timer timer = new Timer(100, evt -> scroll());
071
072    private int scrollDirection = NO_SCROLL;
073
074    public ScrollViewport(JComponent c, int direction) {
075        this(direction);
076        add(c);
077    }
078
079    public ScrollViewport(int direction) {
080        setLayout(new BorderLayout());
081
082        JButton button;
083
084        // UP
085        if ((direction & UP_DIRECTION) != 0) {
086            button = new JButton();
087            button.addMouseListener(new ScrollViewPortMouseListener(UP_DIRECTION));
088            button.setPreferredSize(new Dimension(10, 10));
089            button.setIcon(ImageProvider.get("svpUp"));
090            add(button, BorderLayout.NORTH);
091            buttons.add(button);
092        }
093
094        // DOWN
095        if ((direction & DOWN_DIRECTION) != 0) {
096            button = new JButton();
097            button.addMouseListener(new ScrollViewPortMouseListener(DOWN_DIRECTION));
098            button.setPreferredSize(new Dimension(10, 10));
099            button.setIcon(ImageProvider.get("svpDown"));
100            add(button, BorderLayout.SOUTH);
101            buttons.add(button);
102        }
103
104        // LEFT
105        if ((direction & LEFT_DIRECTION) != 0) {
106            button = new JButton();
107            button.addMouseListener(new ScrollViewPortMouseListener(LEFT_DIRECTION));
108            button.setPreferredSize(new Dimension(10, 10));
109            button.setIcon(ImageProvider.get("svpLeft"));
110            add(button, BorderLayout.WEST);
111            buttons.add(button);
112        }
113
114        // RIGHT
115        if ((direction & RIGHT_DIRECTION) != 0) {
116            button = new JButton();
117            button.addMouseListener(new ScrollViewPortMouseListener(RIGHT_DIRECTION));
118            button.setPreferredSize(new Dimension(10, 10));
119            button.setIcon(ImageProvider.get("svpRight"));
120            add(button, BorderLayout.EAST);
121            buttons.add(button);
122        }
123
124        add(vp, BorderLayout.CENTER);
125
126        this.addComponentListener(new ComponentAdapter() {
127            @Override public void componentResized(ComponentEvent e) {
128                showOrHideButtons();
129            }
130        });
131
132        showOrHideButtons();
133
134        timer.setRepeats(true);
135        timer.setInitialDelay(400);
136    }
137
138    public synchronized void scroll() {
139        int direction = scrollDirection;
140
141        if (component == null || direction == NO_SCROLL)
142            return;
143
144        Rectangle viewRect = vp.getViewRect();
145
146        int deltaX = 0;
147        int deltaY = 0;
148
149        if (direction < LEFT_DIRECTION) {
150            deltaY = viewRect.height * 2 / 7;
151        } else {
152            deltaX = viewRect.width * 2 / 7;
153        }
154
155        switch (direction) {
156        case UP_DIRECTION :
157            deltaY *= -1;
158            break;
159        case LEFT_DIRECTION :
160            deltaX *= -1;
161            break;
162        default: // Do nothing
163        }
164
165        scroll(deltaX, deltaY);
166    }
167
168    public synchronized void scroll(int deltaX, int deltaY) {
169        if (component == null)
170            return;
171        Dimension compSize = component.getSize();
172        Rectangle viewRect = vp.getViewRect();
173
174        int newX = viewRect.x + deltaX;
175        int newY = viewRect.y + deltaY;
176
177        if (newY < 0) {
178            newY = 0;
179        }
180        if (newY > compSize.height - viewRect.height) {
181            newY = compSize.height - viewRect.height;
182        }
183        if (newX < 0) {
184            newX = 0;
185        }
186        if (newX > compSize.width - viewRect.width) {
187            newX = compSize.width - viewRect.width;
188        }
189
190        vp.setViewPosition(new Point(newX, newY));
191    }
192
193    /**
194     * Update the visibility of the buttons
195     * Only show them if the Viewport is too small for the content.
196     */
197    public void showOrHideButtons() {
198        boolean needButtons = vp.getViewSize().height > vp.getViewRect().height ||
199        vp.getViewSize().width > vp.getViewRect().width;
200        for (JButton b : buttons) {
201            b.setVisible(needButtons);
202        }
203    }
204
205    public Rectangle getViewRect() {
206        return vp.getViewRect();
207    }
208
209    public Dimension getViewSize() {
210        return vp.getViewSize();
211    }
212
213    public Point getViewPosition() {
214        return vp.getViewPosition();
215    }
216
217    public void add(JComponent c) {
218        vp.removeAll();
219        this.component = c;
220        vp.add(c);
221    }
222}