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}