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 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 JViewport vp = new JViewport();
068    private JComponent component;
069
070    private List<JButton> buttons = new ArrayList<>();
071
072    private 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        }
170
171        scroll(deltaX, deltaY);
172    }
173
174    public synchronized void scroll(int deltaX, int deltaY) {
175        if (component == null)
176            return;
177        Dimension compSize = component.getSize();
178        Rectangle viewRect = vp.getViewRect();
179
180        int newX = viewRect.x + deltaX;
181        int newY = viewRect.y + deltaY;
182
183        if (newY < 0) {
184            newY = 0;
185        }
186        if (newY > compSize.height - viewRect.height) {
187            newY = compSize.height - viewRect.height;
188        }
189        if (newX < 0) {
190            newX = 0;
191        }
192        if (newX > compSize.width - viewRect.width) {
193            newX = compSize.width - viewRect.width;
194        }
195
196        vp.setViewPosition(new Point(newX, newY));
197    }
198
199    /**
200     * Update the visibility of the buttons
201     * Only show them if the Viewport is too small for the content.
202     */
203    public void showOrHideButtons() {
204        boolean needButtons = vp.getViewSize().height > vp.getViewRect().height ||
205        vp.getViewSize().width > vp.getViewRect().width;
206        for (JButton b : buttons) {
207            b.setVisible(needButtons);
208        }
209    }
210
211    public Rectangle getViewRect() {
212        return vp.getViewRect();
213    }
214
215    public Dimension getViewSize() {
216        return vp.getViewSize();
217    }
218
219    public Point getViewPosition() {
220        return vp.getViewPosition();
221    }
222
223    public void add(JComponent c) {
224        vp.removeAll();
225        this.component = c;
226        vp.add(c);
227    }
228}