001    // License: GPL. Copyright 2007 by Christian Gallioz (aka khris78)
002    
003    package org.openstreetmap.josm.gui;
004    
005    import java.awt.BorderLayout;
006    import java.awt.Dimension;
007    import java.awt.Point;
008    import java.awt.Rectangle;
009    import java.awt.event.ActionEvent;
010    import java.awt.event.ActionListener;
011    import java.awt.event.ComponentAdapter;
012    import java.awt.event.ComponentEvent;
013    import java.awt.event.MouseAdapter;
014    import java.awt.event.MouseEvent;
015    import java.util.ArrayList;
016    import java.util.List;
017    
018    import javax.swing.JButton;
019    import javax.swing.JComponent;
020    import javax.swing.JPanel;
021    import javax.swing.JViewport;
022    import javax.swing.Timer;
023    
024    import org.openstreetmap.josm.tools.ImageProvider;
025    
026    /** A viewport with UP and DOWN arrow buttons, so that the user can make the
027     * content scroll.
028     */
029    public class ScrollViewport extends JPanel {
030    
031        private static final int NO_SCROLL = 0;
032    
033        public static final int UP_DIRECTION = 1;
034        public static final int DOWN_DIRECTION = 2;
035        public static final int LEFT_DIRECTION = 4;
036        public static final int RIGHT_DIRECTION = 8;
037        public static final int VERTICAL_DIRECTION = UP_DIRECTION | DOWN_DIRECTION;
038        public static final int HORIZONTAL_DIRECTION = LEFT_DIRECTION | RIGHT_DIRECTION;
039        public static final int ALL_DIRECTION = HORIZONTAL_DIRECTION | VERTICAL_DIRECTION;
040    
041        private class ScrollViewPortMouseListener extends MouseAdapter {
042            private int direction;
043    
044            public ScrollViewPortMouseListener(int direction) {
045                this.direction = direction;
046            }
047    
048            @Override public void mouseExited(MouseEvent arg0) {
049                ScrollViewport.this.scrollDirection = NO_SCROLL;
050                timer.stop();
051            }
052    
053            @Override public void mouseReleased(MouseEvent arg0) {
054                ScrollViewport.this.scrollDirection = NO_SCROLL;
055                timer.stop();
056            }
057    
058            @Override public void mousePressed(MouseEvent arg0) {
059                ScrollViewport.this.scrollDirection = direction;
060                scroll();
061                timer.restart();
062            }
063    
064        }
065    
066        private JViewport vp = new JViewport();
067        private JComponent component = null;
068    
069        private List<JButton> buttons = new ArrayList<JButton>();
070    
071        private Timer timer = new Timer(100, new ActionListener() {
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    }