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        public ScrollViewPortMouseListener(int direction) {
044            this.direction = direction;
045        }
046
047        @Override public void mouseExited(MouseEvent arg0) {
048            ScrollViewport.this.scrollDirection = NO_SCROLL;
049            timer.stop();
050        }
051
052        @Override 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 JViewport vp = new JViewport();
066    private JComponent component = null;
067
068    private List<JButton> buttons = new ArrayList<JButton>();
069
070    private Timer timer = new Timer(100, new ActionListener() {
071        @Override
072        public void actionPerformed(ActionEvent arg0) {
073            ScrollViewport.this.scroll();
074        }
075    });
076
077    private int scrollDirection = NO_SCROLL;
078
079    public ScrollViewport(JComponent c, int direction) {
080        this(direction);
081        add(c);
082    }
083
084    public ScrollViewport(int direction) {
085        setLayout(new BorderLayout());
086
087        JButton button;
088
089        // UP
090        if ((direction & UP_DIRECTION) > 0) {
091            button = new JButton();
092            button.addMouseListener(new ScrollViewPortMouseListener(UP_DIRECTION));
093            button.setPreferredSize(new Dimension(10,10));
094            button.setIcon(ImageProvider.get("svpUp"));
095            add(button, BorderLayout.NORTH);
096            buttons.add(button);
097        }
098
099        // DOWN
100        if ((direction & DOWN_DIRECTION) > 0) {
101            button = new JButton();
102            button.addMouseListener(new ScrollViewPortMouseListener(DOWN_DIRECTION));
103            button.setPreferredSize(new Dimension(10,10));
104            button.setIcon(ImageProvider.get("svpDown"));
105            add(button, BorderLayout.SOUTH);
106            buttons.add(button);
107        }
108
109        // LEFT
110        if ((direction & LEFT_DIRECTION) > 0) {
111            button = new JButton();
112            button.addMouseListener(new ScrollViewPortMouseListener(LEFT_DIRECTION));
113            button.setPreferredSize(new Dimension(10,10));
114            button.setIcon(ImageProvider.get("svpLeft"));
115            add(button, BorderLayout.WEST);
116            buttons.add(button);
117        }
118
119        // RIGHT
120        if ((direction & RIGHT_DIRECTION) > 0) {
121            button = new JButton();
122            button.addMouseListener(new ScrollViewPortMouseListener(RIGHT_DIRECTION));
123            button.setPreferredSize(new Dimension(10,10));
124            button.setIcon(ImageProvider.get("svpRight"));
125            add(button, BorderLayout.EAST);
126            buttons.add(button);
127        }
128
129        add(vp, BorderLayout.CENTER);
130
131        this.addComponentListener(new ComponentAdapter() {
132            @Override public void  componentResized(ComponentEvent e) {
133                showOrHideButtons();
134            }
135        });
136
137        showOrHideButtons();
138
139        timer.setRepeats(true);
140        timer.setInitialDelay(400);
141    }
142
143    public synchronized void scroll() {
144        int direction = scrollDirection;
145
146        if (component == null || direction == NO_SCROLL)
147            return;
148
149        Rectangle viewRect = vp.getViewRect();
150
151        int deltaX = 0;
152        int deltaY = 0;
153
154        if (direction < LEFT_DIRECTION) {
155            deltaY = viewRect.height * 2 / 7;
156        } else {
157            deltaX = viewRect.width * 2 / 7;
158        }
159
160        switch (direction) {
161        case UP_DIRECTION :
162            deltaY *= -1;
163            break;
164        case LEFT_DIRECTION :
165            deltaX *= -1;
166            break;
167        }
168
169        scroll(deltaX, deltaY);
170    }
171    public synchronized void scroll(int deltaX, int deltaY) {
172        if (component == null)
173            return;
174        Dimension compSize = component.getSize();
175        Rectangle viewRect = vp.getViewRect();
176
177        int newX = viewRect.x + deltaX;
178        int newY = viewRect.y + deltaY;
179
180        if (newY < 0) {
181            newY = 0;
182        }
183        if (newY > compSize.height - viewRect.height) {
184            newY = compSize.height - viewRect.height;
185        }
186        if (newX < 0) {
187            newX = 0;
188        }
189        if (newX > compSize.width - viewRect.width) {
190            newX = compSize.width - viewRect.width;
191        }
192
193        vp.setViewPosition(new Point(newX, newY));
194    }
195
196    /**
197     * Update the visibility of the buttons
198     * Only show them if the Viewport is too small for the content.
199     */
200    public void showOrHideButtons() {
201        boolean needButtons = vp.getViewSize().height > vp.getViewRect().height ||
202        vp.getViewSize().width > vp.getViewRect().width;
203        for (JButton b : buttons) {
204            b.setVisible(needButtons);
205        }
206    }
207
208    public Rectangle getViewRect() {
209        return vp.getViewRect();
210    }
211
212    public Dimension getViewSize() {
213        return vp.getViewSize();
214    }
215
216    public Point getViewPosition() {
217        return vp.getViewPosition();
218    }
219
220    public void add(JComponent c) {
221        vp.removeAll();
222        this.component = c;
223        vp.add(c);
224    }
225}