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}