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}