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