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.ComponentAdapter; 009import java.awt.event.ComponentEvent; 010import java.awt.event.MouseAdapter; 011import java.awt.event.MouseEvent; 012import java.util.ArrayList; 013import java.util.List; 014 015import javax.swing.JButton; 016import javax.swing.JComponent; 017import javax.swing.JPanel; 018import javax.swing.JViewport; 019import javax.swing.Timer; 020 021import org.openstreetmap.josm.tools.ImageProvider; 022 023/** A viewport with UP and DOWN arrow buttons, so that the user can make the 024 * content scroll. 025 */ 026public class ScrollViewport extends JPanel { 027 028 private static final int NO_SCROLL = 0; 029 030 public static final int UP_DIRECTION = 1; 031 public static final int DOWN_DIRECTION = 2; 032 public static final int LEFT_DIRECTION = 4; 033 public static final int RIGHT_DIRECTION = 8; 034 public static final int VERTICAL_DIRECTION = UP_DIRECTION | DOWN_DIRECTION; 035 public static final int HORIZONTAL_DIRECTION = LEFT_DIRECTION | RIGHT_DIRECTION; 036 public static final int ALL_DIRECTION = HORIZONTAL_DIRECTION | VERTICAL_DIRECTION; 037 038 private class ScrollViewPortMouseListener extends MouseAdapter { 039 private final int direction; 040 041 ScrollViewPortMouseListener(int direction) { 042 this.direction = direction; 043 } 044 045 @Override 046 public void mouseExited(MouseEvent arg0) { 047 ScrollViewport.this.scrollDirection = NO_SCROLL; 048 timer.stop(); 049 } 050 051 @Override 052 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 final JViewport vp = new JViewport(); 066 private JComponent component; 067 068 private final List<JButton> buttons = new ArrayList<>(); 069 070 private final Timer timer = new Timer(100, evt -> scroll()); 071 072 private int scrollDirection = NO_SCROLL; 073 074 public ScrollViewport(JComponent c, int direction) { 075 this(direction); 076 add(c); 077 } 078 079 public ScrollViewport(int direction) { 080 setLayout(new BorderLayout()); 081 082 JButton button; 083 084 // UP 085 if ((direction & UP_DIRECTION) != 0) { 086 button = new JButton(); 087 button.addMouseListener(new ScrollViewPortMouseListener(UP_DIRECTION)); 088 button.setPreferredSize(new Dimension(10, 10)); 089 button.setIcon(ImageProvider.get("svpUp")); 090 add(button, BorderLayout.NORTH); 091 buttons.add(button); 092 } 093 094 // DOWN 095 if ((direction & DOWN_DIRECTION) != 0) { 096 button = new JButton(); 097 button.addMouseListener(new ScrollViewPortMouseListener(DOWN_DIRECTION)); 098 button.setPreferredSize(new Dimension(10, 10)); 099 button.setIcon(ImageProvider.get("svpDown")); 100 add(button, BorderLayout.SOUTH); 101 buttons.add(button); 102 } 103 104 // LEFT 105 if ((direction & LEFT_DIRECTION) != 0) { 106 button = new JButton(); 107 button.addMouseListener(new ScrollViewPortMouseListener(LEFT_DIRECTION)); 108 button.setPreferredSize(new Dimension(10, 10)); 109 button.setIcon(ImageProvider.get("svpLeft")); 110 add(button, BorderLayout.WEST); 111 buttons.add(button); 112 } 113 114 // RIGHT 115 if ((direction & RIGHT_DIRECTION) != 0) { 116 button = new JButton(); 117 button.addMouseListener(new ScrollViewPortMouseListener(RIGHT_DIRECTION)); 118 button.setPreferredSize(new Dimension(10, 10)); 119 button.setIcon(ImageProvider.get("svpRight")); 120 add(button, BorderLayout.EAST); 121 buttons.add(button); 122 } 123 124 add(vp, BorderLayout.CENTER); 125 126 this.addComponentListener(new ComponentAdapter() { 127 @Override public void componentResized(ComponentEvent e) { 128 showOrHideButtons(); 129 } 130 }); 131 132 showOrHideButtons(); 133 134 timer.setRepeats(true); 135 timer.setInitialDelay(400); 136 } 137 138 public synchronized void scroll() { 139 int direction = scrollDirection; 140 141 if (component == null || direction == NO_SCROLL) 142 return; 143 144 Rectangle viewRect = vp.getViewRect(); 145 146 int deltaX = 0; 147 int deltaY = 0; 148 149 if (direction < LEFT_DIRECTION) { 150 deltaY = viewRect.height * 2 / 7; 151 } else { 152 deltaX = viewRect.width * 2 / 7; 153 } 154 155 switch (direction) { 156 case UP_DIRECTION : 157 deltaY *= -1; 158 break; 159 case LEFT_DIRECTION : 160 deltaX *= -1; 161 break; 162 default: // Do nothing 163 } 164 165 scroll(deltaX, deltaY); 166 } 167 168 public synchronized void scroll(int deltaX, int deltaY) { 169 if (component == null) 170 return; 171 Dimension compSize = component.getSize(); 172 Rectangle viewRect = vp.getViewRect(); 173 174 int newX = viewRect.x + deltaX; 175 int newY = viewRect.y + deltaY; 176 177 if (newY < 0) { 178 newY = 0; 179 } 180 if (newY > compSize.height - viewRect.height) { 181 newY = compSize.height - viewRect.height; 182 } 183 if (newX < 0) { 184 newX = 0; 185 } 186 if (newX > compSize.width - viewRect.width) { 187 newX = compSize.width - viewRect.width; 188 } 189 190 vp.setViewPosition(new Point(newX, newY)); 191 } 192 193 /** 194 * Update the visibility of the buttons 195 * Only show them if the Viewport is too small for the content. 196 */ 197 public void showOrHideButtons() { 198 boolean needButtons = vp.getViewSize().height > vp.getViewRect().height || 199 vp.getViewSize().width > vp.getViewRect().width; 200 for (JButton b : buttons) { 201 b.setVisible(needButtons); 202 } 203 } 204 205 public Rectangle getViewRect() { 206 return vp.getViewRect(); 207 } 208 209 public Dimension getViewSize() { 210 return vp.getViewSize(); 211 } 212 213 public Point getViewPosition() { 214 return vp.getViewPosition(); 215 } 216 217 public void add(JComponent c) { 218 vp.removeAll(); 219 this.component = c; 220 vp.add(c); 221 } 222}