001 /* 002 * $Id: BusyPainter.java 3288 2009-03-10 14:36:28Z kschaefe $ 003 * 004 * Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle, 005 * Santa Clara, California 95054, U.S.A. All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 020 */ 021 022 package org.jdesktop.swingx.painter; 023 024 import java.awt.Color; 025 import java.awt.Graphics2D; 026 import java.awt.Rectangle; 027 import java.awt.Shape; 028 import java.awt.geom.Ellipse2D; 029 import java.awt.geom.PathIterator; 030 import java.awt.geom.Point2D; 031 import java.awt.geom.RoundRectangle2D; 032 import java.awt.geom.Point2D.Float; 033 import java.util.ArrayList; 034 import java.util.List; 035 import java.util.NoSuchElementException; 036 037 import org.jdesktop.swingx.JXBusyLabel.Direction; 038 import org.jdesktop.swingx.color.ColorUtil; 039 040 /** 041 * A specific painter that paints an "infinite progress" like animation. For 042 * more details see {@link org.jdesktop.swingx.JXBusyLabel} 043 */ 044 public class BusyPainter extends AbstractPainter<Object> { 045 046 private int frame = -1; 047 048 private int points = 8; 049 050 private Color baseColor = new Color(200, 200, 200); 051 052 private Color highlightColor = Color.BLACK; 053 054 private int trailLength = 4; 055 056 private Shape pointShape; 057 058 private Shape trajectory; 059 060 private Direction direction = Direction.RIGHT; 061 062 private boolean paintCentered; 063 064 /** 065 * Creates new busy painter initialized to the shape of circle and bounds size 26x26 points. 066 */ 067 public BusyPainter() { 068 this(26); 069 } 070 071 /** 072 * Creates new painter initialized to the shape of circle and bounds of square of specified height. 073 * @param height Painter height. 074 */ 075 public BusyPainter(int height) { 076 this(getScaledDefaultPoint(height), 077 getScaledDefaultTrajectory(height)); 078 } 079 080 protected static Shape getScaledDefaultTrajectory(int height) { 081 return new Ellipse2D.Float(((height * 8) / 26) / 2, ((height * 8) / 26) / 2, height 082 - ((height * 8) / 26), height - ((height * 8) / 26)); 083 } 084 085 protected static Shape getScaledDefaultPoint(int height) { 086 return new RoundRectangle2D.Float(0, 0, (height * 8) / 26, 4, 087 4, 4); 088 } 089 090 /** 091 * Initializes painter to the specified trajectory and and point shape. Bounds are dynamically calculated to so the specified trajectory fits in. 092 * @param point Point shape. 093 * @param trajectory Trajectory shape. 094 */ 095 public BusyPainter(Shape point, Shape trajectory) { 096 init(point, trajectory, Color.LIGHT_GRAY, Color.BLACK); 097 } 098 099 /** 100 * Initializes painter to provided shapes and default colors. 101 * @param point Point shape. 102 * @param trajectory Trajectory shape. 103 */ 104 protected void init(Shape point, Shape trajectory, Color baseColor, Color highlightColor) { 105 this.baseColor = baseColor; 106 this.highlightColor = highlightColor; 107 this.pointShape = point; 108 this.trajectory = trajectory; 109 } 110 111 /** 112 * @inheritDoc 113 */ 114 @Override 115 protected void doPaint(Graphics2D g, Object t, int width, int height) { 116 Rectangle r = getTrajectory().getBounds(); 117 int tw = width - r.width - 2*r.x; 118 int th = height - r.height - 2*r.y; 119 if (isPaintCentered()) { 120 ((Graphics2D) g).translate(tw/2, th/2); 121 } 122 123 PathIterator pi = trajectory.getPathIterator(null); 124 float[] coords = new float[6]; 125 Float cp = new Point2D.Float(); 126 Point2D.Float sp = new Point2D.Float(); 127 int ret; 128 float totalDist = 0; 129 List<float[]> segStack = new ArrayList<float[]>(); 130 do { 131 try { 132 ret = pi.currentSegment(coords); 133 } catch (NoSuchElementException e) { 134 // invalid object definition - one of the bounds is zero or less 135 return; 136 } 137 if (ret == PathIterator.SEG_LINETO || (ret == PathIterator.SEG_CLOSE && (sp.x != cp.x || sp.y != cp.y))) { 138 //close by line 139 float c = calcLine(coords, cp); 140 totalDist += c; 141 // move the point to the end (just so it is same for all of them 142 segStack.add(new float[] { c, 0, 0, 0, 0, coords[0], coords[1], ret }); 143 cp.x = coords[0]; 144 cp.y = coords[1]; 145 } 146 if (ret == PathIterator.SEG_MOVETO) { 147 sp.x = cp.x = coords[0]; 148 sp.y = cp.y = coords[1]; 149 150 } 151 if (ret == PathIterator.SEG_CUBICTO) { 152 float c = calcCube(coords, cp); 153 totalDist += c; 154 segStack.add(new float[] { c, coords[0], coords[1], coords[2], 155 coords[3], coords[4], coords[5], ret }); 156 cp.x = coords[4]; 157 cp.y = coords[5]; 158 } 159 if (ret == PathIterator.SEG_QUADTO) { 160 float c = calcLengthOfQuad(coords, cp); 161 totalDist += c; 162 segStack.add(new float[] { c, coords[0], coords[1], 0 ,0 , coords[2], 163 coords[3], ret }); 164 cp.x = coords[2]; 165 cp.y = coords[3]; 166 } 167 // got a starting point, center point on it. 168 pi.next(); 169 } while (!pi.isDone()); 170 float nxtP = totalDist / getPoints(); 171 List<Point2D.Float> pList = new ArrayList<Point2D.Float>(); 172 pList.add(new Float(sp.x, sp.y)); 173 int sgIdx = 0; 174 float[] sgmt = segStack.get(sgIdx); 175 float len = sgmt[0]; 176 float travDist = nxtP; 177 Float center = new Float(sp.x, sp.y); 178 for (int i = 1; i < getPoints(); i++) { 179 while (len < nxtP) { 180 sgIdx++; 181 // Be carefull when messing around with points. 182 sp.x = sgmt[5]; 183 sp.y = sgmt[6]; 184 sgmt = segStack.get(sgIdx); 185 travDist = nxtP - len; 186 len += sgmt[0]; 187 } 188 len -= nxtP; 189 Float p = calcPoint(travDist, sp, sgmt, width, height); 190 pList.add(p); 191 center.x += p.x; 192 center.y += p.y; 193 travDist += nxtP; 194 } 195 // calculate center 196 center.x = ((float) width) / 2; 197 center.y = ((float) height) / 2; 198 199 // draw the stuff 200 int i = 0; 201 g.translate(center.x, center.y); 202 for (Point2D.Float p : pList) { 203 drawAt(g, i++, p, center); 204 } 205 g.translate(-center.x, -center.y); 206 207 if (isPaintCentered()) { 208 ((Graphics2D) g).translate(-tw/2, -th/2); 209 } 210 } 211 212 /** 213 * Gets value of centering hint. If true, shape will be positioned in the center of painted area. 214 * @return Whether shape will be centered over painting area or not. 215 */ 216 public boolean isPaintCentered() { 217 return this.paintCentered; 218 } 219 220 private void drawAt(Graphics2D g, int i, Point2D.Float p, Float c) { 221 g.setColor(calcFrameColor(i)); 222 paintRotatedCenteredShapeAtPoint(p, c, g); 223 } 224 225 private void paintRotatedCenteredShapeAtPoint(Float p, Float c, Graphics2D g) { 226 Shape s = getPointShape(); 227 double hh = s.getBounds().getHeight() / 2; 228 double wh = s.getBounds().getWidth() / 2; 229 double t, x, y; 230 double a = c.y - p.y; 231 double b = p.x - c.x; 232 double sa = Math.signum(a); 233 double sb = Math.signum(b); 234 sa = sa == 0 ? 1 : sa; 235 sb = sb == 0 ? 1 : sb; 236 a = Math.abs(a); 237 b = Math.abs(b); 238 t = Math.atan(a / b); 239 t = sa > 0 ? sb > 0 ? -t : -Math.PI + t : sb > 0 ? t : Math.PI - t; 240 x = Math.sqrt(a * a + b * b) - wh; 241 y = -hh; 242 g.rotate(t); 243 g.translate(x, y); 244 g.fill(s); 245 g.translate(-x, -y); 246 g.rotate(-t); 247 248 } 249 250 private Point2D.Float calcPoint(float dist2go, Point2D.Float startPoint, 251 float[] sgmt, int w, int h) { 252 Float f = new Point2D.Float(); 253 if (sgmt[7] == PathIterator.SEG_LINETO) { 254 // linear 255 float a = sgmt[5] - startPoint.x; 256 float b = sgmt[6] - startPoint.y; 257 float pathLen = sgmt[0]; 258 f.x = startPoint.x + a * dist2go / pathLen; 259 f.y = startPoint.y + b * dist2go / pathLen; 260 } else if (sgmt[7] == PathIterator.SEG_QUADTO) { 261 // quadratic curve 262 Float ctrl = new Point2D.Float(sgmt[1]/w, sgmt[2]/h); 263 Float end = new Point2D.Float(sgmt[5]/w, sgmt[6]/h); 264 Float start = new Float(startPoint.x/w, startPoint.y/h); 265 266 // trans coords from abs to rel 267 f = getXY(dist2go / sgmt[0], start, ctrl, end); 268 f.x *= w; 269 f.y *= h; 270 271 } else if (sgmt[7] == PathIterator.SEG_CUBICTO) { 272 // bezier curve 273 float x = Math.abs(startPoint.x - sgmt[5]); 274 float y = Math.abs(startPoint.y - sgmt[6]); 275 276 // trans coords from abs to rel 277 float c1rx = Math.abs(startPoint.x - sgmt[1]) / x; 278 float c1ry = Math.abs(startPoint.y - sgmt[2]) / y; 279 float c2rx = Math.abs(startPoint.x - sgmt[3]) / x; 280 float c2ry = Math.abs(startPoint.y - sgmt[4]) / y; 281 f = getXY(dist2go / sgmt[0], c1rx, c1ry, c2rx, c2ry); 282 283 float a = startPoint.x - sgmt[5]; 284 float b = startPoint.y - sgmt[6]; 285 286 f.x = startPoint.x - f.x * a; 287 f.y = startPoint.y - f.y * b; 288 } 289 return f; 290 } 291 292 293 /** 294 * Calculates length of the linear segment. 295 * @param coords Segment coordinates. 296 * @param cp Start point. 297 * @return Length of the segment. 298 */ 299 private float calcLine(float[] coords, Float cp) { 300 float a = cp.x - coords[0]; 301 float b = cp.y - coords[1]; 302 float c = (float) Math.sqrt(a * a + b * b); 303 return c; 304 } 305 306 /** 307 * Claclulates length of the cubic segment. 308 * @param coords Segment coordinates. 309 * @param cp Start point. 310 * @return Length of the segment. 311 */ 312 private float calcCube(float[] coords, Float cp) { 313 float x = Math.abs(cp.x - coords[4]); 314 float y = Math.abs(cp.y - coords[5]); 315 316 // trans coords from abs to rel 317 float c1rx = Math.abs(cp.x - coords[0]) / x; 318 float c1ry = Math.abs(cp.y - coords[1]) / y; 319 float c2rx = Math.abs(cp.x - coords[2]) / x; 320 float c2ry = Math.abs(cp.y - coords[3]) / y; 321 float prevLength = 0, prevX = 0, prevY = 0; 322 for (float t = 0.01f; t <= 1.0f; t += .01f) { 323 Point2D.Float xy = getXY(t, c1rx, c1ry, c2rx, c2ry); 324 prevLength += (float) Math.sqrt((xy.x - prevX) * (xy.x - prevX) 325 + (xy.y - prevY) * (xy.y - prevY)); 326 prevX = xy.x; 327 prevY = xy.y; 328 } 329 // prev len is a fraction num of the real path length 330 float z = ((Math.abs(x) + Math.abs(y)) / 2) * prevLength; 331 return z; 332 } 333 334 /** 335 * Calculates length of the quadratic segment 336 * @param coords Segment coordinates 337 * @param cp Start point. 338 * @return Length of the segment. 339 */ 340 private float calcLengthOfQuad(float[] coords, Point2D.Float cp) { 341 Float ctrl = new Point2D.Float(coords[0], coords[1]); 342 Float end = new Point2D.Float(coords[2], coords[3]); 343 // get abs values 344 // ctrl1 345 float c1ax = Math.abs(cp.x - ctrl.x) ; 346 float c1ay = Math.abs(cp.y - ctrl.y) ; 347 // end1 348 float e1ax = Math.abs(cp.x - end.x) ; 349 float e1ay = Math.abs(cp.y - end.y) ; 350 // get max value on each axis 351 float maxX = Math.max(c1ax, e1ax); 352 float maxY = Math.max(c1ay, e1ay); 353 354 // trans coords from abs to rel 355 // ctrl1 356 ctrl.x = c1ax / maxX; 357 ctrl.y = c1ay / maxY; 358 // end1 359 end.x = e1ax / maxX; 360 end.y = e1ay / maxY; 361 362 // claculate length 363 float prevLength = 0, prevX = 0, prevY = 0; 364 for (float t = 0.01f; t <= 1.0f; t += .01f) { 365 Point2D.Float xy = getXY(t, new Float(0,0), ctrl, end); 366 prevLength += (float) Math.sqrt((xy.x - prevX) * (xy.x - prevX) 367 + (xy.y - prevY) * (xy.y - prevY)); 368 prevX = xy.x; 369 prevY = xy.y; 370 } 371 // prev len is a fraction num of the real path length 372 float a = Math.abs(coords[2] - cp.x); 373 float b = Math.abs(coords[3] - cp.y); 374 float dist = (float) Math.sqrt(a*a+b*b); 375 return prevLength * dist; 376 } 377 378 /** 379 * Calculates the XY point for a given t value. 380 * 381 * The general spline equation is: x = b0*x0 + b1*x1 + b2*x2 + b3*x3 y = 382 * b0*y0 + b1*y1 + b2*y2 + b3*y3 where: b0 = (1-t)^3 b1 = 3 * t * (1-t)^2 b2 = 383 * 3 * t^2 * (1-t) b3 = t^3 We know that (x0,y0) == (0,0) and (x1,y1) == 384 * (1,1) for our splines, so this simplifies to: x = b1*x1 + b2*x2 + b3 y = 385 * b1*x1 + b2*x2 + b3 386 * 387 * @author chet 388 * 389 * @param t parametric value for spline calculation 390 */ 391 private Point2D.Float getXY(float t, float x1, float y1, float x2, float y2) { 392 Point2D.Float xy; 393 float invT = (1 - t); 394 float b1 = 3 * t * (invT * invT); 395 float b2 = 3 * (t * t) * invT; 396 float b3 = t * t * t; 397 xy = new Point2D.Float((b1 * x1) + (b2 * x2) + b3, (b1 * y1) 398 + (b2 * y2) + b3); 399 return xy; 400 } 401 402 /** 403 * Calculates relative position of the point on the quad curve in time t<0,1>. 404 * @param t distance on the curve 405 * @param ctrl Control point in rel coords 406 * @param end End point in rel coords 407 * @return Solution of the quad equation for time T in non complex space in rel coords. 408 */ 409 public static Point2D.Float getXY(float t, Point2D.Float begin, Point2D.Float ctrl, Point2D.Float end) { 410 /* 411 * P1 = (x1, y1) - start point of curve 412 * P2 = (x2, y2) - end point of curve 413 * Pc = (xc, yc) - control point 414 * 415 * Pq(t) = P1*(1 - t)^2 + 2*Pc*t*(1 - t) + P2*t^2 = 416 * = (P1 - 2*Pc + P2)*t^2 + 2*(Pc - P1)*t + P1 417 * t = [0:1] 418 * // thx Jim ... 419 * 420 * b0 = (1 -t)^2, b1 = 2*t*(1-t), b2 = t^2 421 */ 422 Point2D.Float xy; 423 float invT = (1 - t); 424 float b0 = invT * invT; 425 float b1 = 2 * t * invT ; 426 float b2 = t * t; 427 xy = new Point2D.Float(b0 * begin.x + (b1 * ctrl.x) + b2* end.x, b0 * begin.y + (b1 * ctrl.y) + b2* end.y); 428 429 return xy; 430 } 431 432 /** 433 * Selects appropriate color for given frame based on the frame position and gradient difference. 434 * @param i Frame. 435 * @return Frame color. 436 */ 437 private Color calcFrameColor(final int i) { 438 if (frame == -1) { 439 return getBaseColor(); 440 } 441 442 for (int t = 0; t < getTrailLength(); t++) { 443 if (direction == Direction.RIGHT 444 && i == (frame - t + getPoints()) % getPoints()) { 445 float terp = 1 - ((float) (getTrailLength() - t)) 446 / (float) getTrailLength(); 447 return ColorUtil.interpolate(getBaseColor(), 448 getHighlightColor(), terp); 449 } else if (direction == Direction.LEFT 450 && i == (frame + t) % getPoints()) { 451 float terp = ((float) (t)) / (float) getTrailLength(); 452 return ColorUtil.interpolate(getBaseColor(), 453 getHighlightColor(), terp); 454 } 455 } 456 return getBaseColor(); 457 } 458 459 /** 460 * Gets current frame. 461 * @return Current frame. 462 */ 463 public int getFrame() { 464 return frame; 465 } 466 467 /**Sets current frame. 468 * @param frame Current frame. 469 */ 470 public void setFrame(int frame) { 471 int old = getFrame(); 472 this.frame = frame; 473 firePropertyChange("frame", old, getFrame()); 474 } 475 476 /** 477 * Gets base color. 478 * @return Base color. 479 */ 480 public Color getBaseColor() { 481 return baseColor; 482 } 483 484 /** 485 * Sets new base color. Bound property. 486 * @param baseColor Base color. 487 */ 488 public void setBaseColor(Color baseColor) { 489 Color old = getBaseColor(); 490 this.baseColor = baseColor; 491 firePropertyChange("baseColor", old, getBaseColor()); 492 } 493 494 /** 495 * Gets highlight color. 496 * @return Current highlight color. 497 */ 498 public Color getHighlightColor() { 499 return highlightColor; 500 } 501 502 /** 503 * Sets new highlight color. Bound property. 504 * @param highlightColor New highlight color. 505 */ 506 public void setHighlightColor(Color highlightColor) { 507 Color old = getHighlightColor(); 508 this.highlightColor = highlightColor; 509 firePropertyChange("highlightColor", old, getHighlightColor()); 510 } 511 512 /** 513 * Gets total amount of distinct points in spinner. 514 * @return Total amount of points. 515 */ 516 public int getPoints() { 517 return points; 518 } 519 520 /** 521 * Sets total amount of points in spinner. Bound property. 522 * @param points Total amount of points. 523 */ 524 public void setPoints(int points) { 525 int old = getPoints(); 526 this.points = points; 527 firePropertyChange("points", old, getPoints()); 528 } 529 530 /** 531 * Gets length of trail in number of points. 532 * @return Trail lenght. 533 */ 534 public int getTrailLength() { 535 return trailLength; 536 } 537 538 /** 539 * Sets length of the trail in points. Bound property. 540 * @param trailLength Trail length in points. 541 */ 542 public void setTrailLength(int trailLength) { 543 int old = getTrailLength(); 544 this.trailLength = trailLength; 545 firePropertyChange("trailLength", old, getTrailLength()); 546 } 547 548 /** 549 * Gets shape of current point. 550 * @return Shape of the point. 551 */ 552 public final Shape getPointShape() { 553 return pointShape; 554 } 555 556 /** 557 * Sets new point shape. Bound property. 558 * @param pointShape new Shape. 559 */ 560 public final void setPointShape(Shape pointShape) { 561 Shape old = getPointShape(); 562 this.pointShape = pointShape; 563 if (getPointShape() != old && getPointShape() != null 564 && !getPointShape().equals(old)) { 565 firePropertyChange("pointShape", old, getPointShape()); 566 } 567 } 568 569 /** 570 * Gets current trajectory. 571 * @return Current spinner trajectory . 572 */ 573 public final Shape getTrajectory() { 574 return trajectory; 575 } 576 577 /** 578 * Sets new trajectory. Expected trajectory have to be closed shape. Bound property. 579 * @param trajectory New trajectory. 580 */ 581 public final void setTrajectory(Shape trajectory) { 582 Shape old = getTrajectory(); 583 this.trajectory = trajectory; 584 if (getTrajectory() != old && getTrajectory() != null 585 && !getTrajectory().equals(old)) { 586 firePropertyChange("trajectory", old, getTrajectory()); 587 } 588 } 589 590 /** 591 * Sets new spinning direction. 592 * @param dir Spinning direction. 593 */ 594 public void setDirection(Direction dir) { 595 Direction old = getDirection(); 596 this.direction = dir; 597 if (getDirection() != old && getDirection() != null 598 && !getDirection().equals(old)) { 599 firePropertyChange("direction", old, getDirection()); 600 } 601 } 602 603 /** 604 * Gets current direction of spinning. 605 * @return Current spinning direction. 606 */ 607 public Direction getDirection() { 608 return this.direction; 609 } 610 611 protected Shape provideShape(Graphics2D g, Object comp, int width, int height) { 612 return new Rectangle(0,0,width,height); 613 } 614 615 /** 616 * Centers shape in the area covered by the painter. 617 * @param paintCentered Centering hint. 618 */ 619 public void setPaintCentered(boolean paintCentered) { 620 boolean old = isPaintCentered(); 621 this.paintCentered = paintCentered; 622 firePropertyChange("paintCentered", old, isPaintCentered()); 623 } 624 625 }