1/* 2 * Copyright (c) 1998, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25package com.sun.hotspot.igv.util; 26 27import com.sun.hotspot.igv.data.ChangedListener; 28import java.awt.*; 29import java.awt.geom.*; 30import java.awt.event.MouseEvent; 31import java.awt.event.MouseListener; 32import java.awt.event.MouseMotionListener; 33import java.util.List; 34import javax.swing.*; 35 36/** 37 * 38 * @author Thomas Wuerthinger 39 */ 40public class RangeSlider extends JComponent implements ChangedListener<RangeSliderModel>, MouseListener, MouseMotionListener, Scrollable { 41 42 public static final int HEIGHT = 40; 43 public static final float BAR_HEIGHT = 22; 44 public static final float BAR_SELECTION_ENDING_HEIGHT = 16; 45 public static final float BAR_SELECTION_HEIGHT = 10; 46 public static final float BAR_THICKNESS = 2; 47 public static final float BAR_CIRCLE_SIZE = 9; 48 public static final float BAR_CIRCLE_CONNECTOR_SIZE = 6; 49 public static final int MOUSE_ENDING_OFFSET = 3; 50 public static final Color BACKGROUND_COLOR = Color.white; 51 public static final Color BAR_COLOR = Color.black; 52 public static final Color BAR_SELECTION_COLOR = new Color(255, 0, 0, 120); 53 public static final Color BAR_SELECTION_COLOR_ROLLOVER = new Color(255, 0, 255, 120); 54 public static final Color BAR_SELECTION_COLOR_DRAG = new Color(0, 0, 255, 120); 55 private RangeSliderModel model; 56 private State state; 57 private Point startPoint; 58 private RangeSliderModel tempModel; 59 private boolean isOverBar; 60 61 private enum State { 62 63 Initial, 64 DragBar, 65 DragFirstPosition, 66 DragSecondPosition 67 } 68 69 public RangeSlider() { 70 state = State.Initial; 71 this.addMouseMotionListener(this); 72 this.addMouseListener(this); 73 } 74 75 public void setModel(RangeSliderModel newModel) { 76 if (model != null) { 77 model.getChangedEvent().removeListener(this); 78 model.getColorChangedEvent().removeListener(this); 79 } 80 if (newModel != null) { 81 newModel.getChangedEvent().addListener(this); 82 newModel.getColorChangedEvent().addListener(this); 83 } 84 this.model = newModel; 85 update(); 86 } 87 88 private RangeSliderModel getPaintingModel() { 89 if (tempModel != null) { 90 return tempModel; 91 } 92 return model; 93 } 94 95 /** 96 * Returns the preferred size of the viewport for a view component. 97 * For example, the preferred size of a <code>JList</code> component 98 * is the size required to accommodate all of the cells in its list. 99 * However, the value of <code>preferredScrollableViewportSize</code> 100 * is the size required for <code>JList.getVisibleRowCount</code> rows. 101 * A component without any properties that would affect the viewport 102 * size should just return <code>getPreferredSize</code> here. 103 * 104 * @return the preferredSize of a <code>JViewport</code> whose view 105 * is this <code>Scrollable</code> 106 * @see JViewport#getPreferredSize 107 */ 108 public Dimension getPreferredScrollableViewportSize() { 109 return getPreferredSize(); 110 } 111 112 public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { 113 if (orientation == SwingConstants.VERTICAL) { 114 return 1; 115 } 116 117 return (int)(BAR_CIRCLE_SIZE + BAR_CIRCLE_CONNECTOR_SIZE); 118 } 119 120 public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { 121 return orientation == SwingConstants.VERTICAL ? visibleRect.height / 2 : visibleRect.width / 2; 122 } 123 124 public boolean getScrollableTracksViewportWidth() { 125 return false; 126 } 127 128 public boolean getScrollableTracksViewportHeight() { 129 return true; 130 } 131 132 @Override 133 public Dimension getPreferredSize() { 134 Dimension d = super.getPreferredSize(); 135 d.height = HEIGHT; 136 d.width = Math.max(d.width, (int)(2 * BAR_CIRCLE_CONNECTOR_SIZE + getPaintingModel().getPositions().size() * (BAR_CIRCLE_SIZE + BAR_CIRCLE_CONNECTOR_SIZE))); 137 return d; 138 } 139 140 @Override 141 public void changed(RangeSliderModel source) { 142 revalidate(); 143 144 float barStartY = getBarStartY(); 145 int circleCenterY = (int)(barStartY + BAR_HEIGHT / 2); 146 int startX = (int)getStartXPosition(model.getFirstPosition()); 147 int endX = (int)getEndXPosition(model.getSecondPosition()); 148 Rectangle r = new Rectangle(startX, circleCenterY, endX - startX, 1); 149 scrollRectToVisible(r); 150 update(); 151 } 152 153 private void update() { 154 this.repaint(); 155 } 156 157 private float getXPosition(int index) { 158 assert index >= 0 && index < getPaintingModel().getPositions().size(); 159 return getXOffset() * (index + 1); 160 } 161 162 private float getXOffset() { 163 int size = getPaintingModel().getPositions().size(); 164 float width = (float)getWidth(); 165 return (width / (size + 1)); 166 } 167 168 private float getEndXPosition(int index) { 169 return getXPosition(index) + getXOffset() / 2; 170 } 171 172 private float getStartXPosition(int index) { 173 return getXPosition(index) - getXOffset() / 2; 174 } 175 176 @Override 177 public void paint(Graphics g) { 178 super.paint(g); 179 Graphics2D g2 = (Graphics2D) g; 180 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 181 RenderingHints.VALUE_ANTIALIAS_ON); 182 int width = getWidth(); 183 int height = getHeight(); 184 185 g2.setColor(BACKGROUND_COLOR); 186 g2.fill(new Rectangle2D.Float(0, 0, width, height)); 187 188 // Nothing to paint? 189 if (getPaintingModel() == null || getPaintingModel().getPositions().size() == 0) { 190 return; 191 } 192 193 int firstPos = getPaintingModel().getFirstPosition(); 194 int secondPos = getPaintingModel().getSecondPosition(); 195 196 paintSelected(g2, firstPos, secondPos); 197 paintBar(g2); 198 199 } 200 201 private float getBarStartY() { 202 return getHeight() / 2 - BAR_HEIGHT / 2; 203 } 204 205 private void paintBar(Graphics2D g) { 206 List<String> list = getPaintingModel().getPositions(); 207 float barStartY = getBarStartY(); 208 209 g.setColor(BAR_COLOR); 210 g.fill(new Rectangle2D.Float(getXPosition(0), barStartY + BAR_HEIGHT / 2 - BAR_THICKNESS / 2, getXPosition(list.size() - 1) - getXPosition(0), BAR_THICKNESS)); 211 212 float circleCenterY = barStartY + BAR_HEIGHT / 2; 213 for (int i = 0; i < list.size(); i++) { 214 float curX = getXPosition(i); 215 g.setColor(getPaintingModel().getColors().get(i)); 216 g.fill(new Ellipse2D.Float(curX - BAR_CIRCLE_SIZE / 2, circleCenterY - BAR_CIRCLE_SIZE / 2, BAR_CIRCLE_SIZE, BAR_CIRCLE_SIZE)); 217 g.setColor(Color.black); 218 g.draw(new Ellipse2D.Float(curX - BAR_CIRCLE_SIZE / 2, circleCenterY - BAR_CIRCLE_SIZE / 2, BAR_CIRCLE_SIZE, BAR_CIRCLE_SIZE)); 219 220 221 String curS = list.get(i); 222 if (curS != null && curS.length() > 0) { 223 float startX = getStartXPosition(i); 224 float endX = getEndXPosition(i); 225 FontMetrics metrics = g.getFontMetrics(); 226 Rectangle bounds = metrics.getStringBounds(curS, g).getBounds(); 227 if (bounds.width < endX - startX && bounds.height < barStartY) { 228 g.setColor(Color.black); 229 g.drawString(curS, startX + (endX - startX) / 2 - bounds.width / 2, barStartY / 2 + bounds.height / 2); 230 } 231 } 232 } 233 234 } 235 236 private void paintSelected(Graphics2D g, int start, int end) { 237 238 float startX = getStartXPosition(start); 239 float endX = getEndXPosition(end); 240 float barStartY = getBarStartY(); 241 float barSelectionEndingStartY = barStartY + BAR_HEIGHT / 2 - BAR_SELECTION_ENDING_HEIGHT / 2; 242 paintSelectedEnding(g, startX, barSelectionEndingStartY); 243 paintSelectedEnding(g, endX, barSelectionEndingStartY); 244 245 g.setColor(BAR_SELECTION_COLOR); 246 if (state == State.DragBar) { 247 g.setColor(BAR_SELECTION_COLOR_DRAG); 248 } else if (isOverBar) { 249 g.setColor(BAR_SELECTION_COLOR_ROLLOVER); 250 } 251 g.fill(new Rectangle2D.Float(startX, barStartY + BAR_HEIGHT / 2 - BAR_SELECTION_HEIGHT / 2, endX - startX, BAR_SELECTION_HEIGHT)); 252 } 253 254 private void paintSelectedEnding(Graphics2D g, float x, float y) { 255 g.setColor(BAR_COLOR); 256 g.fill(new Rectangle2D.Float(x - BAR_THICKNESS / 2, y, BAR_THICKNESS, BAR_SELECTION_ENDING_HEIGHT)); 257 } 258 259 private boolean isOverSecondPosition(Point p) { 260 if (p.y >= getBarStartY()) { 261 float destX = getEndXPosition(getPaintingModel().getSecondPosition()); 262 float off = Math.abs(destX - p.x); 263 return off <= MOUSE_ENDING_OFFSET; 264 } 265 return false; 266 } 267 268 private boolean isOverFirstPosition(Point p) { 269 if (p.y >= getBarStartY()) { 270 float destX = getStartXPosition(getPaintingModel().getFirstPosition()); 271 float off = Math.abs(destX - p.x); 272 return off <= MOUSE_ENDING_OFFSET; 273 } 274 return false; 275 } 276 277 private boolean isOverSelection(Point p) { 278 if (p.y >= getBarStartY() && !isOverFirstPosition(p) && !isOverSecondPosition(p)) { 279 return p.x > getStartXPosition(getPaintingModel().getFirstPosition()) && p.x < getEndXPosition(getPaintingModel().getSecondPosition()); 280 } 281 return false; 282 } 283 284 @Override 285 public void mouseDragged(MouseEvent e) { 286 Rectangle r = new Rectangle(e.getX(), e.getY(), 1, 1); 287 scrollRectToVisible(r); 288 289 if (state == State.DragBar) { 290 float firstX = this.getStartXPosition(model.getFirstPosition()); 291 float newFirstX = firstX + e.getPoint().x - startPoint.x; 292 int newIndex = getIndexFromPosition(newFirstX) + 1; 293 if (newIndex + model.getSecondPosition() - model.getFirstPosition() >= model.getPositions().size()) { 294 newIndex = model.getPositions().size() - (model.getSecondPosition() - model.getFirstPosition()) - 1; 295 } 296 int secondPosition = newIndex + model.getSecondPosition() - model.getFirstPosition(); 297 tempModel.setPositions(newIndex, secondPosition); 298 update(); 299 } else if (state == State.DragFirstPosition) { 300 int firstPosition = getIndexFromPosition(e.getPoint().x) + 1; 301 int secondPosition = model.getSecondPosition(); 302 if (firstPosition > secondPosition) { 303 firstPosition--; 304 } 305 tempModel.setPositions(firstPosition, secondPosition); 306 update(); 307 } else if (state == State.DragSecondPosition) { 308 int firstPosition = model.getFirstPosition(); 309 int secondPosition = getIndexFromPosition(e.getPoint().x); 310 if (secondPosition < firstPosition) { 311 secondPosition++; 312 } 313 tempModel.setPositions(firstPosition, secondPosition); 314 update(); 315 } 316 } 317 318 private int getIndexFromPosition(float x) { 319 if (x < getXPosition(0)) { 320 return -1; 321 } 322 for (int i = 0; i < getPaintingModel().getPositions().size() - 1; i++) { 323 float startX = getXPosition(i); 324 float endX = getXPosition(i + 1); 325 if (x >= startX && x <= endX) { 326 return i; 327 } 328 } 329 return getPaintingModel().getPositions().size() - 1; 330 } 331 332 private int getCircleIndexFromPosition(int x) { 333 int result = 0; 334 for (int i = 1; i < getPaintingModel().getPositions().size(); i++) { 335 if (x > getStartXPosition(i)) { 336 result = i; 337 } 338 } 339 return result; 340 } 341 342 @Override 343 public void mouseMoved(MouseEvent e) { 344 isOverBar = false; 345 if (model == null) { 346 return; 347 } 348 349 350 Point p = e.getPoint(); 351 if (isOverFirstPosition(p) || isOverSecondPosition(p)) { 352 setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR)); 353 } else if (isOverSelection(p)) { 354 isOverBar = true; 355 setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); 356 } else { 357 this.setCursor(Cursor.getDefaultCursor()); 358 } 359 repaint(); 360 } 361 362 @Override 363 public void mouseClicked(MouseEvent e) { 364 if (e.getClickCount() > 1) { 365 // Double click 366 int index = getCircleIndexFromPosition(e.getPoint().x); 367 model.setPositions(index, index); 368 } 369 } 370 371 @Override 372 public void mousePressed(MouseEvent e) { 373 if (model == null) { 374 return; 375 } 376 377 Point p = e.getPoint(); 378 if (isOverFirstPosition(p)) { 379 state = State.DragFirstPosition; 380 } else if (isOverSecondPosition(p)) { 381 state = State.DragSecondPosition; 382 } else if (isOverSelection(p)) { 383 state = State.DragBar; 384 } else { 385 return; 386 } 387 388 startPoint = e.getPoint(); 389 tempModel = model.copy(); 390 } 391 392 @Override 393 public void mouseReleased(MouseEvent e) { 394 if (model == null || tempModel == null) { 395 return; 396 } 397 state = State.Initial; 398 model.setPositions(tempModel.getFirstPosition(), tempModel.getSecondPosition()); 399 tempModel = null; 400 } 401 402 @Override 403 public void mouseEntered(MouseEvent e) { 404 } 405 406 @Override 407 public void mouseExited(MouseEvent e) { 408 isOverBar = false; 409 repaint(); 410 } 411} 412