1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.bric.multislider;
21
22 import java.awt.Component;
23 import java.awt.Dimension;
24 import java.awt.Graphics;
25 import java.awt.Graphics2D;
26 import java.awt.Insets;
27 import java.awt.Rectangle;
28 import java.awt.RenderingHints;
29 import java.awt.Shape;
30 import java.awt.Toolkit;
31 import java.awt.event.ComponentEvent;
32 import java.awt.event.ComponentListener;
33 import java.awt.event.FocusEvent;
34 import java.awt.event.FocusListener;
35 import java.awt.event.KeyEvent;
36 import java.awt.event.KeyListener;
37 import java.awt.event.MouseEvent;
38 import java.awt.event.MouseListener;
39 import java.awt.event.MouseMotionListener;
40 import java.awt.geom.AffineTransform;
41 import java.awt.geom.Ellipse2D;
42 import java.awt.geom.GeneralPath;
43 import java.awt.geom.Point2D;
44 import java.awt.geom.Rectangle2D;
45 import java.beans.PropertyChangeEvent;
46 import java.beans.PropertyChangeListener;
47 import java.lang.reflect.Array;
48 import java.util.LinkedHashSet;
49 import java.util.Set;
50
51 import javax.swing.JComponent;
52 import javax.swing.SwingConstants;
53 import javax.swing.UIManager;
54 import javax.swing.plaf.ComponentUI;
55
56 import com.bric.multislider.MultiThumbSlider.Collision;
57
58
59
60
61
62 public abstract class MultiThumbSliderUi<T> extends ComponentUI implements MouseListener, MouseMotionListener
63 {
64
65
66
67
68
69 public static final String THUMB_SHAPE_PROPERTY = MultiThumbSliderUi.class.getName() + ".thumbShape";
70
71 PropertyChangeListener thumbShapeListener = new PropertyChangeListener()
72 {
73
74 @Override
75 public void propertyChange(PropertyChangeEvent evt)
76 {
77 MultiThumbSliderUi.this.slider.repaint();
78 }
79
80 };
81
82
83
84
85 public static enum Thumb
86 {
87 Circle()
88 {
89 @Override
90 public Shape getShape(float width, float height, boolean leftEdge, boolean rightEdge)
91 {
92 Ellipse2D e = new Ellipse2D.Float(-width / 2f, -height / 2f, width, height);
93 return e;
94 }
95 },
96 Triangle()
97 {
98 @Override
99 public Shape getShape(float width, float height, boolean leftEdge, boolean rightEdge)
100 {
101 float k = width / 2;
102 GeneralPath p = new GeneralPath();
103 float r = 5;
104
105 if ((leftEdge) && (!rightEdge))
106 {
107 k = k * 2;
108 p.moveTo(0, height / 2);
109 p.lineTo(-k, height / 2 - k);
110 p.lineTo(-k, -height / 2 + r);
111 p.curveTo(-k, -height / 2, -k, -height / 2, -k + r, -height / 2);
112 p.lineTo(0, -height / 2);
113 p.closePath();
114 }
115 else if ((rightEdge) && (!leftEdge))
116 {
117 k = k * 2;
118 p.moveTo(0, -height / 2);
119 p.lineTo(k - r, -height / 2);
120 p.curveTo(k, -height / 2, k, -height / 2, k, -height / 2 + r);
121 p.lineTo(k, height / 2 - k);
122 p.lineTo(0, height / 2);
123 p.closePath();
124 }
125 else
126 {
127 p.moveTo(0, height / 2);
128 p.lineTo(-k, height / 2 - k);
129 p.lineTo(-k, -height / 2 + r);
130 p.curveTo(-k, -height / 2, -k, -height / 2, -k + r, -height / 2);
131 p.lineTo(k - r, -height / 2);
132 p.curveTo(k, -height / 2, k, -height / 2, k, -height / 2 + r);
133 p.lineTo(k, height / 2 - k);
134 p.closePath();
135 }
136 return p;
137 }
138 },
139 Rectangle()
140 {
141 @Override
142 public Shape getShape(float width, float height, boolean leftEdge, boolean rightEdge)
143 {
144 if ((leftEdge) && (!rightEdge))
145 {
146 return new Rectangle2D.Float(-width, -height / 2, width, height);
147 }
148 else if ((rightEdge) && (!leftEdge))
149 {
150 return new Rectangle2D.Float(0, -height / 2, width, height);
151 }
152 else
153 {
154 return new Rectangle2D.Float(-width / 2, -height / 2, width, height);
155 }
156 }
157 },
158 Hourglass()
159 {
160 @Override
161 public Shape getShape(float width, float height, boolean leftEdge, boolean rightEdge)
162 {
163 GeneralPath p = new GeneralPath();
164 if ((leftEdge) && (!rightEdge))
165 {
166 float k = width;
167 p.moveTo(-width, -height / 2);
168 p.lineTo(0, -height / 2);
169 p.lineTo(0, height / 2);
170 p.lineTo(-width, height / 2);
171 p.lineTo(0, height / 2 - k);
172 p.lineTo(0, -height / 2 + k);
173 p.closePath();
174 }
175 else if ((rightEdge) && (!leftEdge))
176 {
177 float k = width;
178 p.moveTo(width, -height / 2);
179 p.lineTo(0, -height / 2);
180 p.lineTo(0, height / 2);
181 p.lineTo(width, height / 2);
182 p.lineTo(0, height / 2 - k);
183 p.lineTo(0, -height / 2 + k);
184 p.closePath();
185 }
186 else
187 {
188 float k = width / 2;
189 p.moveTo(-width / 2, -height / 2);
190 p.lineTo(width / 2, -height / 2);
191 p.lineTo(0, -height / 2 + k);
192 p.lineTo(0, height / 2 - k);
193 p.lineTo(width / 2, height / 2);
194 p.lineTo(-width / 2, height / 2);
195 p.lineTo(0, height / 2 - k);
196 p.lineTo(0, -height / 2 + k);
197 p.closePath();
198 }
199 return p;
200 }
201 };
202
203
204
205
206
207
208
209
210
211
212
213
214 public Shape getShape(MultiThumbSliderUi<?> sliderUI, float x, float y, int width, int height, boolean leftEdge,
215 boolean rightEdge)
216 {
217
218
219
220
221 GeneralPath path = new GeneralPath(getShape(width, height, false, false));
222 if (sliderUI.slider.getOrientation() == SwingConstants.VERTICAL)
223 {
224 path.transform(AffineTransform.getRotateInstance(-Math.PI / 2));
225 }
226 path.transform(AffineTransform.getTranslateInstance(x, y));
227 return path;
228 }
229
230
231
232
233
234
235
236
237
238 public abstract Shape getShape(float width, float height, boolean leftEdge, boolean rightEdge);
239 }
240
241 protected MultiThumbSlider<T> slider;
242
243
244
245
246 int MAX_LENGTH = 300;
247
248
249
250
251 int MIN_LENGTH = 50;
252
253
254
255
256
257 int PREF_LENGTH = 140;
258
259
260
261
262 int DEPTH = 15;
263
264
265
266
267
268 int[] thumbPositions = new int[0];
269
270
271
272
273 protected float[] thumbIndications = new float[0];
274
275
276 private float indicationGoal = 0;
277
278
279
280
281 float indication = 0;
282
283
284 protected Rectangle trackRect = new Rectangle(0, 0, 0, 0);
285
286 public MultiThumbSliderUi(MultiThumbSlider<T> slider)
287 {
288 this.slider = slider;
289 }
290
291 @Override
292 public Dimension getMaximumSize(JComponent s)
293 {
294 MultiThumbSlider<T> mySlider = (MultiThumbSlider<T>) s;
295 int k = Math.max(this.DEPTH, getPreferredComponentDepth());
296 if (mySlider.getOrientation() == MultiThumbSlider.HORIZONTAL)
297 {
298 return new Dimension(this.MAX_LENGTH, k);
299 }
300 return new Dimension(k, this.MAX_LENGTH);
301 }
302
303 @Override
304 public Dimension getMinimumSize(JComponent s)
305 {
306 MultiThumbSlider<T> mySlider = (MultiThumbSlider<T>) s;
307 int k = Math.max(this.DEPTH, getPreferredComponentDepth());
308 if (mySlider.getOrientation() == MultiThumbSlider.HORIZONTAL)
309 {
310 return new Dimension(this.MIN_LENGTH, k);
311 }
312 return new Dimension(k, this.MIN_LENGTH);
313 }
314
315 @Override
316 public Dimension getPreferredSize(JComponent s)
317 {
318 MultiThumbSlider<T> mySlider = (MultiThumbSlider<T>) s;
319 int k = Math.max(this.DEPTH, getPreferredComponentDepth());
320 if (mySlider.getOrientation() == MultiThumbSlider.HORIZONTAL)
321 {
322 return new Dimension(this.PREF_LENGTH, k);
323 }
324 return new Dimension(k, this.PREF_LENGTH);
325 }
326
327
328
329
330
331 protected abstract int getPreferredComponentDepth();
332
333
334
335
336
337 class State
338 {
339 T[] values;
340
341 float[] positions;
342
343 int selectedThumb;
344
345 public State()
346 {
347 this.values = MultiThumbSliderUi.this.slider.getValues();
348 this.positions = MultiThumbSliderUi.this.slider.getThumbPositions();
349 this.selectedThumb = MultiThumbSliderUi.this.slider.getSelectedThumb(false);
350 }
351
352 public State(State s)
353 {
354 this.selectedThumb = s.selectedThumb;
355 this.positions = new float[s.positions.length];
356 this.values = createSimilarArray(s.values, s.values.length);
357 System.arraycopy(s.positions, 0, this.positions, 0, this.positions.length);
358 System.arraycopy(s.values, 0, this.values, 0, this.values.length);
359 }
360
361
362 private void polish()
363 {
364 while (this.positions[0] < 0)
365 {
366 float[] f2 = new float[this.positions.length - 1];
367 System.arraycopy(this.positions, 1, f2, 0, this.positions.length - 1);
368 T[] c2 = createSimilarArray(this.values, this.values.length - 1);
369 System.arraycopy(this.values, 1, c2, 0, this.positions.length - 1);
370 this.positions = f2;
371 this.values = c2;
372 this.selectedThumb++;
373 }
374 while (this.positions[this.positions.length - 1] > 1)
375 {
376 float[] f2 = new float[this.positions.length - 1];
377 System.arraycopy(this.positions, 0, f2, 0, this.positions.length - 1);
378 T[] c2 = createSimilarArray(this.values, this.values.length - 1);
379 System.arraycopy(this.values, 0, c2, 0, this.positions.length - 1);
380 this.positions = f2;
381 this.values = c2;
382 this.selectedThumb--;
383 }
384 if (this.selectedThumb >= this.positions.length)
385 this.selectedThumb = -1;
386 }
387
388
389 public void install()
390 {
391 polish();
392
393 MultiThumbSliderUi.this.slider.setValues(this.positions, this.values);
394 MultiThumbSliderUi.this.slider.setSelectedThumb(this.selectedThumb);
395 }
396
397
398
399
400
401
402
403 private T[] createSimilarArray(T[] src, int length)
404 {
405 Class<?> componentType = src.getClass().getComponentType();
406 return (T[]) Array.newInstance(componentType, length);
407 }
408
409 public void removeThumb(int index)
410 {
411 float[] f = new float[this.positions.length - 1];
412 T[] c = createSimilarArray(this.values, this.values.length - 1);
413 System.arraycopy(this.positions, 0, f, 0, index);
414 System.arraycopy(this.values, 0, c, 0, index);
415 System.arraycopy(this.positions, index + 1, f, index, f.length - index);
416 System.arraycopy(this.values, index + 1, c, index, f.length - index);
417 this.positions = f;
418 this.values = c;
419 this.selectedThumb = -1;
420 }
421
422 public boolean setPosition(int thumbIndex, float newPosition)
423 {
424 return setPosition(thumbIndex, newPosition, true);
425 }
426
427 private boolean isCrossover(int thumbIndexA, int thumbIndexB, float newThumbBPosition)
428 {
429 if (thumbIndexA == thumbIndexB)
430 return false;
431 int oldState = new Float(this.positions[thumbIndexA]).compareTo(this.positions[thumbIndexB]);
432 int newState = new Float(this.positions[thumbIndexA]).compareTo(newThumbBPosition);
433 if (newState * oldState < 0)
434 return true;
435 return isOverlap(thumbIndexA, thumbIndexB, newThumbBPosition);
436 }
437
438 private boolean isOverlap(int thumbIndexA, int thumbIndexB, float newThumbBPosition)
439 {
440 if (thumbIndexA == thumbIndexB)
441 return false;
442 if (!MultiThumbSliderUi.this.slider.isThumbOverlap())
443 {
444 Point2D aCenter = getThumbCenter(this.positions[thumbIndexA]);
445 Point2D bCenter = getThumbCenter(newThumbBPosition);
446 Rectangle2D aBounds = ShapeBounds.getBounds(getThumbShape(thumbIndexA, aCenter));
447 Rectangle2D bBounds = ShapeBounds.getBounds(getThumbShape(thumbIndexB, bCenter));
448 return aBounds.intersects(bBounds) || aBounds.equals(bBounds);
449 }
450 return false;
451 }
452
453 private boolean setPosition(int thumbIndex, float newPosition, boolean revise)
454 {
455 Collision c = MultiThumbSliderUi.this.slider.getCollisionPolicy();
456 if (Collision.JUMP_OVER_OTHER.equals(c) && (!MultiThumbSliderUi.this.slider.isThumbOverlap()))
457 {
458 newPosition = Math.max(0, Math.min(1, newPosition));
459 for (int a = 0; a < this.positions.length; a++)
460 {
461 if (isOverlap(a, thumbIndex, newPosition))
462 {
463 if (revise)
464 {
465 float alternative;
466
467 int maxWidth = Math.max(getThumbSize(a).width, getThumbSize(thumbIndex).width);
468 float trackSize = MultiThumbSliderUi.this.slider.getOrientation() == SwingConstants.HORIZONTAL
469 ? MultiThumbSliderUi.this.trackRect.width : MultiThumbSliderUi.this.trackRect.height;
470 newPosition = Math.max(0, Math.min(1, newPosition));
471
472 for (int offset = 0; offset < 4 * maxWidth; offset++)
473 {
474 alternative = Math.max(0, Math.min(1, newPosition - ((float) offset) / trackSize));
475 if (!isOverlap(a, thumbIndex, alternative))
476 {
477 return setPosition(thumbIndex, alternative, false);
478 }
479 alternative = Math.max(0, Math.min(1, newPosition + ((float) offset) / trackSize));
480 if (!isOverlap(a, thumbIndex, alternative))
481 {
482 return setPosition(thumbIndex, alternative, false);
483 }
484 }
485 return false;
486 }
487 return false;
488 }
489 }
490 }
491 else if (Collision.STOP_AGAINST.equals(c))
492 {
493 for (int a = 0; a < this.positions.length; a++)
494 {
495 if (isCrossover(a, thumbIndex, newPosition))
496 {
497
498 if (revise)
499 {
500 float alternative;
501
502 int maxWidth = Math.max(getThumbSize(a).width, getThumbSize(thumbIndex).width);
503 float trackSize = MultiThumbSliderUi.this.slider.getOrientation() == SwingConstants.HORIZONTAL
504 ? MultiThumbSliderUi.this.trackRect.width : MultiThumbSliderUi.this.trackRect.height;
505
506 for (int offset = 0; offset < 2 * maxWidth; offset++)
507 {
508 if (this.positions[a] > this.positions[thumbIndex])
509 {
510 alternative = this.positions[a] - ((float) offset) / trackSize;
511 }
512 else
513 {
514 alternative = this.positions[a] + ((float) offset) / trackSize;
515 }
516 if (!isCrossover(a, thumbIndex, alternative))
517 {
518 return setPosition(thumbIndex, alternative, false);
519 }
520 }
521 return false;
522 }
523
524 return false;
525 }
526 }
527 }
528 else if (Collision.NUDGE_OTHER.equals(c))
529 {
530 if (revise)
531 {
532 final Set<Integer> processedThumbs = new LinkedHashSet<Integer>();
533 processedThumbs.add(-1);
534
535 class NudgeRequest
536 {
537
538 final int thumbIndex;
539
540
541 final float startingValue;
542
543
544 final float requestedDelta;
545
546 NudgeRequest(int thumbIndex, float startingValue, float requestedDelta)
547 {
548 this.thumbIndex = thumbIndex;
549 this.startingValue = startingValue;
550 this.requestedDelta = requestedDelta;
551 }
552
553 void process()
554 {
555 float span;
556 if (MultiThumbSliderUi.this.slider.isThumbOverlap())
557 {
558 span = 0;
559 }
560 else
561 {
562 span = (float) ShapeBounds.getBounds(getThumbShape(this.thumbIndex)).getWidth();
563 if (MultiThumbSliderUi.this.slider.getOrientation() == SwingConstants.HORIZONTAL)
564 {
565 span = span / ((float) MultiThumbSliderUi.this.trackRect.width);
566 }
567 else
568 {
569 span = span / ((float) MultiThumbSliderUi.this.trackRect.height);
570 }
571 }
572 int[] neighbors = getNeighbors(this.thumbIndex);
573 float newPosition = this.startingValue + this.requestedDelta;
574 processedThumbs.add(this.thumbIndex);
575
576 if (neighbors[0] == -1 && newPosition < 0)
577 {
578 setPosition(this.thumbIndex, 0, false);
579 }
580 else if (neighbors[1] == -1 && newPosition > 1)
581 {
582 setPosition(this.thumbIndex, 1, false);
583 }
584 else if (processedThumbs.add(neighbors[0]) && (newPosition < State.this.positions[neighbors[0]]
585 || Math.abs(State.this.positions[neighbors[0]] - newPosition) < span - .0001))
586 {
587 NudgeRequest dependsOn = new NudgeRequest(neighbors[0], State.this.positions[neighbors[0]],
588 (newPosition - span) - State.this.positions[neighbors[0]]);
589 dependsOn.process();
590 setPosition(this.thumbIndex, State.this.positions[dependsOn.thumbIndex] + span, false);
591 }
592 else if (processedThumbs.add(neighbors[1]) && (newPosition > State.this.positions[neighbors[1]]
593 || Math.abs(State.this.positions[neighbors[1]] - newPosition) < span - .0001))
594 {
595 NudgeRequest dependsOn = new NudgeRequest(neighbors[1], State.this.positions[neighbors[1]],
596 (newPosition + span) - State.this.positions[neighbors[1]]);
597 dependsOn.process();
598 setPosition(this.thumbIndex, State.this.positions[dependsOn.thumbIndex] - span, false);
599 }
600 else
601 {
602 setPosition(this.thumbIndex, this.startingValue + this.requestedDelta, false);
603 }
604 }
605 }
606
607 float originalValue = this.positions[thumbIndex];
608 NudgeRequest rootRequest =
609 new NudgeRequest(thumbIndex, this.positions[thumbIndex], newPosition - this.positions[thumbIndex]);
610 rootRequest.process();
611 return this.positions[thumbIndex] != originalValue;
612 }
613
614 }
615 this.positions[thumbIndex] = newPosition;
616 return true;
617 }
618
619
620
621
622
623
624 int[] getNeighbors(int thumbIndex)
625 {
626 float leftNeighborDelta = 10;
627 float rightNeighborDelta = 10;
628 int leftNeighbor = -1;
629 int rightNeighbor = -1;
630 for (int a = 0; a < this.positions.length; a++)
631 {
632 if (a != thumbIndex)
633 {
634 if (this.positions[thumbIndex] < this.positions[a])
635 {
636 float delta = this.positions[a] - this.positions[thumbIndex];
637 if (delta < rightNeighborDelta)
638 {
639 rightNeighborDelta = delta;
640 rightNeighbor = a;
641 }
642 }
643 else if (this.positions[thumbIndex] > this.positions[a])
644 {
645 float delta = this.positions[thumbIndex] - this.positions[a];
646 if (delta < leftNeighborDelta)
647 {
648 leftNeighborDelta = delta;
649 leftNeighbor = a;
650 }
651 }
652 }
653 }
654 return new int[] {leftNeighbor, rightNeighbor};
655 }
656 }
657
658 Thread animatingThread = null;
659
660 Runnable animatingRunnable = new Runnable()
661 {
662 public void run()
663 {
664 boolean finished = false;
665 while (!finished)
666 {
667 synchronized (MultiThumbSliderUi.this)
668 {
669 finished = true;
670 for (int a = 0; a < MultiThumbSliderUi.this.thumbIndications.length; a++)
671 {
672 if (a != MultiThumbSliderUi.this.slider.getSelectedThumb())
673 {
674 if (a == MultiThumbSliderUi.this.currentIndicatedThumb)
675 {
676 if (MultiThumbSliderUi.this.thumbIndications[a] < 1)
677 {
678 MultiThumbSliderUi.this.thumbIndications[a] =
679 Math.min(1, MultiThumbSliderUi.this.thumbIndications[a] + .025f);
680 finished = false;
681 }
682 }
683 else
684 {
685 if (MultiThumbSliderUi.this.thumbIndications[a] > 0)
686 {
687 MultiThumbSliderUi.this.thumbIndications[a] =
688 Math.max(0, MultiThumbSliderUi.this.thumbIndications[a] - .025f);
689 finished = false;
690 }
691 }
692 }
693 else
694 {
695
696
697
698
699 if (a == MultiThumbSliderUi.this.currentIndicatedThumb)
700 {
701 MultiThumbSliderUi.this.thumbIndications[a] = 1;
702 }
703 else
704 {
705 MultiThumbSliderUi.this.thumbIndications[a] = 0;
706 }
707 }
708 }
709 if (MultiThumbSliderUi.this.indicationGoal > MultiThumbSliderUi.this.indication + .01f)
710 {
711 if (MultiThumbSliderUi.this.indication < .99f)
712 {
713 MultiThumbSliderUi.this.indication = Math.min(1, MultiThumbSliderUi.this.indication + .1f);
714 finished = false;
715 }
716 }
717 else if (MultiThumbSliderUi.this.indicationGoal < MultiThumbSliderUi.this.indication - .01f)
718 {
719 if (MultiThumbSliderUi.this.indication > .01f)
720 {
721 MultiThumbSliderUi.this.indication = Math.max(0, MultiThumbSliderUi.this.indication - .1f);
722 finished = false;
723 }
724 }
725 }
726 if (!finished)
727 MultiThumbSliderUi.this.slider.repaint();
728
729
730 long t = System.currentTimeMillis();
731 while (System.currentTimeMillis() - t < 20)
732 {
733 try
734 {
735 Thread.sleep(10);
736 }
737 catch (Exception e)
738 {
739 Thread.yield();
740 }
741 }
742 }
743 }
744 };
745
746 private int currentIndicatedThumb = -1;
747
748 protected boolean mouseInside = false;
749
750 protected boolean mouseIsDown = false;
751
752 private State pressedState;
753
754 private int dx, dy;
755
756 public void mousePressed(MouseEvent e)
757 {
758 this.dx = 0;
759 this.dy = 0;
760
761 if (this.slider.isEnabled() == false)
762 return;
763
764 if (e.getClickCount() >= 2)
765 {
766 if (this.slider.doDoubleClick(e.getX(), e.getY()))
767 {
768 e.consume();
769 return;
770 }
771 }
772 else if (e.isPopupTrigger())
773 {
774 int x = e.getX();
775 int y = e.getY();
776 if (this.slider.getOrientation() == MultiThumbSlider.HORIZONTAL)
777 {
778 if (x < this.trackRect.x || x > this.trackRect.x + this.trackRect.width)
779 return;
780 y = this.trackRect.y + this.trackRect.height;
781 }
782 else
783 {
784 if (y < this.trackRect.y || y > this.trackRect.y + this.trackRect.height)
785 return;
786 x = this.trackRect.x + this.trackRect.width;
787 }
788 if (this.slider.doPopup(x, y))
789 {
790 e.consume();
791 return;
792 }
793 }
794 this.mouseIsDown = true;
795 mouseMoved(e);
796
797 if (e.getSource() != this.slider)
798 {
799 throw new RuntimeException("only install this UI on the GradientSlider it was constructed with");
800 }
801 this.slider.requestFocus();
802
803 int index = getIndex(e);
804 if (index != -1)
805 {
806 if (this.slider.getOrientation() == SwingConstants.HORIZONTAL)
807 {
808 this.dx = -e.getX() + this.thumbPositions[index];
809 }
810 else
811 {
812 this.dy = -e.getY() + this.thumbPositions[index];
813 }
814 }
815
816 if (index != -1)
817 {
818 this.slider.setSelectedThumb(index);
819 e.consume();
820 }
821 else
822 {
823 if (this.slider.isAutoAdding())
824 {
825 float k;
826
827 int v;
828 if (this.slider.getOrientation() == MultiThumbSlider.HORIZONTAL)
829 {
830 v = e.getX();
831 }
832 else
833 {
834 v = e.getY();
835 }
836
837 if (this.slider.getOrientation() == MultiThumbSlider.HORIZONTAL)
838 {
839 k = ((float) (v - this.trackRect.x)) / ((float) this.trackRect.width);
840 if (this.slider.isInverted())
841 k = 1 - k;
842 }
843 else
844 {
845 k = ((float) (v - this.trackRect.y)) / ((float) this.trackRect.height);
846 if (this.slider.isInverted() == false)
847 k = 1 - k;
848 }
849 if (k > 0 && k < 1)
850 {
851 int added = this.slider.addThumb(k);
852 this.slider.setSelectedThumb(added);
853 }
854 e.consume();
855 }
856 else
857 {
858 if (this.slider.getSelectedThumb() != -1)
859 {
860 this.slider.setSelectedThumb(-1);
861 e.consume();
862 }
863 }
864 }
865 this.pressedState = new State();
866 }
867
868 private int getIndex(MouseEvent e)
869 {
870 int v;
871 Rectangle2D shapeSum =
872 new Rectangle2D.Double(this.trackRect.x, this.trackRect.y, this.trackRect.width, this.trackRect.height);
873 for (int a = 0; a < this.slider.getThumbCount(); a++)
874 {
875 shapeSum.add(ShapeBounds.getBounds(getThumbShape(a)));
876 }
877 if (this.slider.getOrientation() == MultiThumbSlider.HORIZONTAL)
878 {
879 v = e.getX();
880 if (v < shapeSum.getMinX() || v > shapeSum.getMaxX())
881 {
882 return -1;
883 }
884 }
885 else
886 {
887 v = e.getY();
888 if (v < shapeSum.getMinY() || v > shapeSum.getMaxY())
889 {
890 return -1;
891 }
892 }
893 int min = Math.abs(v - this.thumbPositions[0]);
894 int minIndex = 0;
895 for (int a = 1; a < this.thumbPositions.length; a++)
896 {
897 int distance = Math.abs(v - this.thumbPositions[a]);
898 if (distance < min)
899 {
900 min = distance;
901 minIndex = a;
902 }
903 else if (distance == min)
904 {
905
906 if (v < this.thumbPositions[a])
907 {
908
909 if (this.slider.isInverted())
910 {
911
912 minIndex = a;
913 }
914 }
915 else
916 {
917 if (!this.slider.isInverted())
918 minIndex = a;
919 }
920 }
921 }
922 if (min < getThumbSize(minIndex).width / 2)
923 {
924 return minIndex;
925 }
926 return -1;
927 }
928
929 public void mouseEntered(MouseEvent e)
930 {
931 mouseMoved(e);
932 }
933
934 public void mouseExited(MouseEvent e)
935 {
936 setCurrentIndicatedThumb(-1);
937 setMouseInside(false);
938 }
939
940 public void mouseClicked(MouseEvent e)
941 {
942 }
943
944 public void mouseMoved(MouseEvent e)
945 {
946 if (this.slider.isEnabled() == false)
947 return;
948
949 int i = getIndex(e);
950 setCurrentIndicatedThumb(i);
951 boolean b = (e.getX() >= 0 && e.getX() < this.slider.getWidth() && e.getY() >= 0 && e.getY() < this.slider.getHeight());
952 if (this.mouseIsDown)
953 b = true;
954 setMouseInside(b);
955 }
956
957 protected Dimension getThumbSize(int thumbIndex)
958 {
959 return new Dimension(16, 16);
960 }
961
962
963
964
965
966
967
968
969 public Shape getThumbShape(int thumbIndex)
970 {
971 return getThumbShape(thumbIndex, null);
972 }
973
974
975
976
977
978
979
980
981
982
983
984 public Shape getThumbShape(int thumbIndex, Point2D center)
985 {
986 Thumb thumb = getThumb(thumbIndex);
987 if (center == null)
988 center = getThumbCenter(thumbIndex);
989 Dimension d = getThumbSize(thumbIndex);
990 return thumb.getShape(this, (float) center.getX(), (float) center.getY(), d.width, d.height, thumbIndex == 0,
991 thumbIndex == this.slider.getThumbCount() - 1);
992 }
993
994
995
996
997
998
999 public Point2D getThumbCenter(int thumbIndex)
1000 {
1001 float[] values = this.slider.getThumbPositions();
1002 float n = values[thumbIndex];
1003
1004 return getThumbCenter(n);
1005 }
1006
1007
1008
1009
1010
1011
1012 public Point2D getThumbCenter(float position)
1013 {
1014
1015
1016
1017
1018 if (position < 0 || position > 1)
1019 return null;
1020
1021 if (this.slider.getOrientation() == MultiThumbSlider.VERTICAL)
1022 {
1023 float y;
1024 float height = (float) this.trackRect.height;
1025 float x = (float) this.trackRect.getCenterX();
1026 if (this.slider.isInverted())
1027 {
1028 y = (float) (position * height + this.trackRect.y);
1029 }
1030 else
1031 {
1032 y = (float) ((1 - position) * height + this.trackRect.y);
1033 }
1034 return new Point2D.Float(x, y);
1035 }
1036 else
1037 {
1038 float x;
1039 float width = (float) this.trackRect.width;
1040 float y = (float) this.trackRect.getCenterY();
1041 if (this.slider.isInverted())
1042 {
1043 x = (float) ((1 - position) * width + this.trackRect.x);
1044 }
1045 else
1046 {
1047 x = (float) (position * width + this.trackRect.x);
1048 }
1049 return new Point2D.Float(x, y);
1050 }
1051 }
1052
1053
1054
1055
1056
1057
1058
1059 public Thumb getThumb(int thumbIndex)
1060 {
1061 Thumb thumb = getProperty(this.slider, THUMB_SHAPE_PROPERTY, Thumb.Circle);
1062 return thumb;
1063 }
1064
1065 private void setCurrentIndicatedThumb(int i)
1066 {
1067 if (getProperty(this.slider, "MultiThumbSlider.indicateThumb", "true").equals("false"))
1068 {
1069
1070 i = -1;
1071 }
1072 this.currentIndicatedThumb = i;
1073 boolean finished = true;
1074 for (int a = 0; a < this.thumbIndications.length; a++)
1075 {
1076 if (a == this.currentIndicatedThumb)
1077 {
1078 if (this.thumbIndications[a] != 1)
1079 {
1080 finished = false;
1081 }
1082 }
1083 else
1084 {
1085 if (this.thumbIndications[a] != 0)
1086 {
1087 finished = false;
1088 }
1089 }
1090 }
1091 if (!finished)
1092 {
1093 synchronized (MultiThumbSliderUi.this)
1094 {
1095 if (this.animatingThread == null || this.animatingThread.isAlive() == false)
1096 {
1097 this.animatingThread = new Thread(this.animatingRunnable);
1098 this.animatingThread.start();
1099 }
1100 }
1101 }
1102 }
1103
1104 private void setMouseInside(boolean b)
1105 {
1106 this.mouseInside = b;
1107 updateIndication();
1108 }
1109
1110 public void mouseDragged(MouseEvent e)
1111 {
1112 if (this.slider.isEnabled() == false)
1113 return;
1114
1115 e.translatePoint(this.dx, this.dy);
1116
1117 mouseMoved(e);
1118 if (this.pressedState != null && this.pressedState.selectedThumb != -1)
1119 {
1120 this.slider.setValueIsAdjusting(true);
1121
1122 State newState = new State(this.pressedState);
1123 float v;
1124 boolean outside;
1125 if (this.slider.getOrientation() == MultiThumbSlider.HORIZONTAL)
1126 {
1127 v = ((float) (e.getX() - this.trackRect.x)) / ((float) this.trackRect.width);
1128 if (this.slider.isInverted())
1129 v = 1 - v;
1130 outside = (e.getY() < this.trackRect.y - 10) || (e.getY() > this.trackRect.y + this.trackRect.height + 10);
1131
1132
1133 if (e.getX() > this.trackRect.x - 10 && e.getX() < this.trackRect.x + this.trackRect.width + 10)
1134 {
1135 if (v < 0)
1136 v = 0;
1137 if (v > 1)
1138 v = 1;
1139 }
1140 }
1141 else
1142 {
1143 v = ((float) (e.getY() - this.trackRect.y)) / ((float) this.trackRect.height);
1144 if (this.slider.isInverted() == false)
1145 v = 1 - v;
1146 outside = (e.getX() < this.trackRect.x - 10) || (e.getX() > this.trackRect.x + this.trackRect.width + 10);
1147
1148 if (e.getY() > this.trackRect.y - 10 && e.getY() < this.trackRect.y + this.trackRect.height + 10)
1149 {
1150 if (v < 0)
1151 v = 0;
1152 if (v > 1)
1153 v = 1;
1154 }
1155 }
1156 if (newState.positions.length <= this.slider.getMinimumThumbnailCount())
1157 {
1158 outside = false;
1159 }
1160 newState.setPosition(newState.selectedThumb, v);
1161
1162
1163 if (outside && this.slider.isThumbRemovalAllowed())
1164 {
1165 newState.removeThumb(newState.selectedThumb);
1166 }
1167 if (validatePositions(newState))
1168 {
1169 newState.install();
1170 }
1171 e.consume();
1172 }
1173 }
1174
1175 public void mouseReleased(MouseEvent e)
1176 {
1177 if (this.slider.isEnabled() == false)
1178 return;
1179
1180 this.mouseIsDown = false;
1181 if (this.pressedState != null && this.slider.getThumbCount() <= this.pressedState.positions.length)
1182 {
1183 mouseDragged(e);
1184 }
1185 if (this.slider.isValueAdjusting())
1186 {
1187 this.slider.setValueIsAdjusting(false);
1188 }
1189 this.slider.repaint();
1190
1191 if (e.isPopupTrigger() && this.slider.doPopup(e.getX(), e.getY()))
1192 {
1193
1194 e.consume();
1195 return;
1196 }
1197 }
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208 public static <K> K getProperty(JComponent jc, String propertyName, K defaultValue)
1209 {
1210 Object jcValue = jc.getClientProperty(propertyName);
1211 if (jcValue != null)
1212 return (K) jcValue;
1213 Object uiValue = UIManager.get(propertyName);
1214 if (uiValue != null)
1215 return (K) uiValue;
1216 return defaultValue;
1217 }
1218
1219
1220
1221
1222
1223
1224 protected boolean validatePositions(State state)
1225 {
1226 float[] p = state.positions;
1227 Object[] c = state.values;
1228
1229
1230
1231
1232
1233 if (p.length <= this.slider.getMinimumThumbnailCount() || (!this.slider.isThumbRemovalAllowed()))
1234 {
1235
1236
1237
1238
1239 for (int a = 0; a < p.length; a++)
1240 {
1241 if (p[a] < 0)
1242 {
1243 p[a] = 0;
1244 }
1245 else if (p[a] > 1)
1246 {
1247 p[a] = 1;
1248 }
1249 }
1250 }
1251
1252
1253 boolean checkAgain = true;
1254 while (checkAgain)
1255 {
1256 checkAgain = false;
1257 for (int a = 0; a < p.length - 1; a++)
1258 {
1259 if (p[a] > p[a + 1])
1260 {
1261 checkAgain = true;
1262
1263 float swap1 = p[a];
1264 p[a] = p[a + 1];
1265 p[a + 1] = swap1;
1266 Object swap2 = c[a];
1267 c[a] = c[a + 1];
1268 c[a + 1] = swap2;
1269
1270 if (a == state.selectedThumb)
1271 {
1272 state.selectedThumb = a + 1;
1273 }
1274 else if (a + 1 == state.selectedThumb)
1275 {
1276 state.selectedThumb = a;
1277 }
1278 }
1279 }
1280 }
1281
1282 return true;
1283 }
1284
1285 FocusListener focusListener = new FocusListener()
1286 {
1287 public void focusLost(FocusEvent e)
1288 {
1289 Component c = (Component) e.getSource();
1290 if (getProperty(MultiThumbSliderUi.this.slider, "MultiThumbSlider.indicateComponent", "false").toString()
1291 .equals("true"))
1292 {
1293 MultiThumbSliderUi.this.slider.setSelectedThumb(-1);
1294 }
1295 updateIndication();
1296 c.repaint();
1297 }
1298
1299 public void focusGained(FocusEvent e)
1300 {
1301 Component c = (Component) e.getSource();
1302 int i = MultiThumbSliderUi.this.slider.getSelectedThumb(false);
1303 if (i == -1)
1304 {
1305 int direction = 1;
1306 if (MultiThumbSliderUi.this.slider.getOrientation() == MultiThumbSlider.VERTICAL)
1307 direction *= -1;
1308 if (MultiThumbSliderUi.this.slider.isInverted())
1309 direction *= -1;
1310 MultiThumbSliderUi.this.slider
1311 .setSelectedThumb((direction == 1) ? 0 : MultiThumbSliderUi.this.slider.getThumbCount() - 1);
1312 }
1313 updateIndication();
1314 c.repaint();
1315 }
1316 };
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326 protected boolean addThumb(int index1, int index2)
1327 {
1328 float pos1 = 0;
1329 float pos2 = 1;
1330 int min;
1331 int max;
1332 if (index1 < index2)
1333 {
1334 min = index1;
1335 max = index2;
1336 }
1337 else
1338 {
1339 min = index2;
1340 max = index1;
1341 }
1342 float[] positions = this.slider.getThumbPositions();
1343 if (min >= 0)
1344 pos1 = positions[min];
1345 if (max < positions.length)
1346 pos2 = positions[max];
1347
1348 if (pos2 - pos1 < .05)
1349 return false;
1350
1351 float newPosition = (pos1 + pos2) / 2f;
1352 this.slider.setSelectedThumb(this.slider.addThumb(newPosition));
1353
1354 return true;
1355 }
1356
1357 KeyListener keyListener = new KeyListener()
1358 {
1359 public void keyPressed(KeyEvent e)
1360 {
1361 if (MultiThumbSliderUi.this.slider.isEnabled() == false)
1362 return;
1363
1364 if (e.getSource() != MultiThumbSliderUi.this.slider)
1365 throw new RuntimeException("only install this UI on the GradientSlider it was constructed with");
1366 int i = MultiThumbSliderUi.this.slider.getSelectedThumb();
1367 int code = e.getKeyCode();
1368 int orientation = MultiThumbSliderUi.this.slider.getOrientation();
1369 if (i != -1 && (code == KeyEvent.VK_RIGHT || code == KeyEvent.VK_LEFT) && orientation == MultiThumbSlider.HORIZONTAL
1370 && e.getModifiers() == Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())
1371 {
1372
1373 int i2;
1374 if ((code == KeyEvent.VK_RIGHT && MultiThumbSliderUi.this.slider.isInverted() == false)
1375 || (code == KeyEvent.VK_LEFT && MultiThumbSliderUi.this.slider.isInverted() == true))
1376 {
1377 i2 = i + 1;
1378 }
1379 else
1380 {
1381 i2 = i - 1;
1382 }
1383 addThumb(i, i2);
1384 e.consume();
1385 return;
1386 }
1387 else if (i != -1 && (code == KeyEvent.VK_UP || code == KeyEvent.VK_DOWN) && orientation == MultiThumbSlider.VERTICAL
1388 && e.getModifiers() == Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())
1389 {
1390
1391 int i2;
1392 if ((code == KeyEvent.VK_UP && MultiThumbSliderUi.this.slider.isInverted() == false)
1393 || (code == KeyEvent.VK_DOWN && MultiThumbSliderUi.this.slider.isInverted() == true))
1394 {
1395 i2 = i + 1;
1396 }
1397 else
1398 {
1399 i2 = i - 1;
1400 }
1401 addThumb(i, i2);
1402 e.consume();
1403 return;
1404 }
1405 else if (code == KeyEvent.VK_DOWN && orientation == MultiThumbSlider.HORIZONTAL && i != -1)
1406 {
1407
1408 int x = MultiThumbSliderUi.this.slider.isInverted()
1409 ? (int) (MultiThumbSliderUi.this.trackRect.x + MultiThumbSliderUi.this.trackRect.width
1410 * (1 - MultiThumbSliderUi.this.slider.getThumbPositions()[i]))
1411 : (int) (MultiThumbSliderUi.this.trackRect.x + MultiThumbSliderUi.this.trackRect.width
1412 * MultiThumbSliderUi.this.slider.getThumbPositions()[i]);
1413 int y = MultiThumbSliderUi.this.trackRect.y + MultiThumbSliderUi.this.trackRect.height;
1414 if (MultiThumbSliderUi.this.slider.doPopup(x, y))
1415 {
1416 e.consume();
1417 return;
1418 }
1419 }
1420 else if (code == KeyEvent.VK_RIGHT && orientation == MultiThumbSlider.VERTICAL && i != -1)
1421 {
1422
1423 int y = MultiThumbSliderUi.this.slider.isInverted()
1424 ? (int) (MultiThumbSliderUi.this.trackRect.y + MultiThumbSliderUi.this.trackRect.height
1425 * MultiThumbSliderUi.this.slider.getThumbPositions()[i])
1426 : (int) (MultiThumbSliderUi.this.trackRect.y + MultiThumbSliderUi.this.trackRect.height
1427 * (1 - MultiThumbSliderUi.this.slider.getThumbPositions()[i]));
1428 int x = MultiThumbSliderUi.this.trackRect.x + MultiThumbSliderUi.this.trackRect.width;
1429 if (MultiThumbSliderUi.this.slider.doPopup(x, y))
1430 {
1431 e.consume();
1432 return;
1433 }
1434 }
1435 if (i != -1)
1436 {
1437
1438 if (code == KeyEvent.VK_RIGHT || code == KeyEvent.VK_DOWN)
1439 {
1440 nudge(i, 1);
1441 e.consume();
1442 }
1443 else if (code == KeyEvent.VK_LEFT || code == KeyEvent.VK_UP)
1444 {
1445 nudge(i, -1);
1446 e.consume();
1447 }
1448 else if (code == KeyEvent.VK_DELETE || code == KeyEvent.VK_BACK_SPACE)
1449 {
1450 if (MultiThumbSliderUi.this.slider.getThumbCount() > MultiThumbSliderUi.this.slider
1451 .getMinimumThumbnailCount() && MultiThumbSliderUi.this.slider.isThumbRemovalAllowed())
1452 {
1453 MultiThumbSliderUi.this.slider.removeThumb(i);
1454 e.consume();
1455 }
1456 }
1457 else if (code == KeyEvent.VK_SPACE || code == KeyEvent.VK_ENTER)
1458 {
1459 MultiThumbSliderUi.this.slider.doDoubleClick(-1, -1);
1460 }
1461 }
1462 }
1463
1464 public void keyReleased(KeyEvent e)
1465 {
1466 }
1467
1468 public void keyTyped(KeyEvent e)
1469 {
1470 }
1471 };
1472
1473 PropertyChangeListener propertyListener = new PropertyChangeListener()
1474 {
1475
1476 public void propertyChange(PropertyChangeEvent e)
1477 {
1478 String name = e.getPropertyName();
1479 if (name.equals(MultiThumbSlider.VALUES_PROPERTY) || name.equals(MultiThumbSlider.ORIENTATION_PROPERTY)
1480 || name.equals(MultiThumbSlider.INVERTED_PROPERTY))
1481 {
1482 calculateGeometry();
1483 MultiThumbSliderUi.this.slider.repaint();
1484 }
1485 else if (name.equals(MultiThumbSlider.SELECTED_THUMB_PROPERTY)
1486 || name.equals(MultiThumbSlider.PAINT_TICKS_PROPERTY))
1487 {
1488 MultiThumbSliderUi.this.slider.repaint();
1489 }
1490 else if (name.equals("MultiThumbSlider.indicateComponent"))
1491 {
1492 setMouseInside(MultiThumbSliderUi.this.mouseInside);
1493 MultiThumbSliderUi.this.slider.repaint();
1494 }
1495 }
1496
1497 };
1498
1499 ComponentListener compListener = new ComponentListener()
1500 {
1501
1502 public void componentHidden(ComponentEvent e)
1503 {
1504 }
1505
1506 public void componentMoved(ComponentEvent e)
1507 {
1508 }
1509
1510 public void componentResized(ComponentEvent e)
1511 {
1512 calculateGeometry();
1513 Component c = (Component) e.getSource();
1514 c.repaint();
1515 }
1516
1517 public void componentShown(ComponentEvent e)
1518 {
1519 }
1520 };
1521
1522 protected void updateIndication()
1523 {
1524 synchronized (MultiThumbSliderUi.this)
1525 {
1526 if (this.slider.isEnabled() && (this.slider.hasFocus() || this.mouseInside))
1527 {
1528 this.indicationGoal = 1;
1529 }
1530 else
1531 {
1532 this.indicationGoal = 0;
1533 }
1534
1535 if (getProperty(this.slider, "MultiThumbSlider.indicateComponent", "false").equals("false"))
1536 {
1537
1538 this.indicationGoal = 1;
1539 if (this.slider.isVisible() == false)
1540 {
1541 this.indication = 1;
1542 }
1543 }
1544
1545 if (this.indication != this.indicationGoal)
1546 {
1547 if (this.animatingThread == null || this.animatingThread.isAlive() == false)
1548 {
1549 this.animatingThread = new Thread(this.animatingRunnable);
1550 this.animatingThread.start();
1551 }
1552 }
1553 }
1554 }
1555
1556 protected synchronized void calculateGeometry()
1557 {
1558 this.trackRect = calculateTrackRect();
1559
1560 float[] pos = this.slider.getThumbPositions();
1561
1562 if (this.thumbPositions.length != pos.length)
1563 {
1564 this.thumbPositions = new int[pos.length];
1565 this.thumbIndications = new float[pos.length];
1566 }
1567 if (this.slider.getOrientation() == MultiThumbSlider.HORIZONTAL)
1568 {
1569 for (int a = 0; a < this.thumbPositions.length; a++)
1570 {
1571 if (this.slider.isInverted() == false)
1572 {
1573 this.thumbPositions[a] = this.trackRect.x + (int) (this.trackRect.width * pos[a]);
1574 }
1575 else
1576 {
1577 this.thumbPositions[a] = this.trackRect.x + (int) (this.trackRect.width * (1 - pos[a]));
1578 }
1579 this.thumbIndications[a] = 0;
1580 }
1581 }
1582 else
1583 {
1584 for (int a = 0; a < this.thumbPositions.length; a++)
1585 {
1586 if (this.slider.isInverted())
1587 {
1588 this.thumbPositions[a] = this.trackRect.y + (int) (this.trackRect.height * pos[a]);
1589 }
1590 else
1591 {
1592 this.thumbPositions[a] = this.trackRect.y + (int) (this.trackRect.height * (1 - pos[a]));
1593 }
1594 this.thumbIndications[a] = 0;
1595 }
1596 }
1597 }
1598
1599 protected Rectangle calculateTrackRect()
1600 {
1601 Insets i = new Insets(5, 5, 5, 5);
1602 int w, h;
1603 if (this.slider.getOrientation() == MultiThumbSlider.HORIZONTAL)
1604 {
1605 w = this.slider.getWidth() - i.left - i.right;
1606 h = Math.min(this.DEPTH, this.slider.getHeight() - i.top - i.bottom);
1607 }
1608 else
1609 {
1610 h = this.slider.getHeight() - i.top - i.bottom;
1611 w = Math.min(this.DEPTH, this.slider.getWidth() - i.left - i.right);
1612 }
1613 return new Rectangle(this.slider.getWidth() / 2 - w / 2, this.slider.getHeight() / 2 - h / 2, w, h);
1614 }
1615
1616 private void nudge(int thumbIndex, int direction)
1617 {
1618 float pixelFraction;
1619 if (this.slider.getOrientation() == MultiThumbSlider.HORIZONTAL)
1620 {
1621 pixelFraction = 1f / (this.trackRect.width);
1622 }
1623 else
1624 {
1625 pixelFraction = 1f / (this.trackRect.height);
1626 }
1627 if (direction < 0)
1628 pixelFraction *= -1;
1629 if (this.slider.isInverted())
1630 pixelFraction *= -1;
1631 if (this.slider.getOrientation() == MultiThumbSlider.VERTICAL)
1632 pixelFraction *= -1;
1633
1634
1635
1636
1637
1638 State state = new State();
1639 int a = 0;
1640 while (a < 10 && state.positions[thumbIndex] >= 0 && state.positions[thumbIndex] <= 1)
1641 {
1642 state.setPosition(thumbIndex, state.positions[thumbIndex] + pixelFraction);
1643 if (validatePositions(state))
1644 {
1645 state.install();
1646 return;
1647 }
1648 a++;
1649 }
1650 }
1651
1652 @Override
1653 public void installUI(JComponent slider)
1654 {
1655 slider.addMouseListener(this);
1656 slider.addMouseMotionListener(this);
1657 slider.addFocusListener(this.focusListener);
1658 slider.addKeyListener(this.keyListener);
1659 slider.addComponentListener(this.compListener);
1660 slider.addPropertyChangeListener(this.propertyListener);
1661 slider.addPropertyChangeListener(THUMB_SHAPE_PROPERTY, this.thumbShapeListener);
1662 calculateGeometry();
1663 }
1664
1665 @Override
1666 public void paint(Graphics g, JComponent slider2)
1667 {
1668 if (slider2 != this.slider)
1669 throw new RuntimeException("only use this UI on the GradientSlider it was constructed with");
1670
1671 Graphics2D g2 = (Graphics2D) g;
1672 int w = this.slider.getWidth();
1673 int h = this.slider.getHeight();
1674
1675 if (this.slider.isOpaque())
1676 {
1677 g.setColor(this.slider.getBackground());
1678 g.fillRect(0, 0, w, h);
1679 }
1680
1681 if (slider2.hasFocus())
1682 {
1683 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
1684 paintFocus(g2);
1685 }
1686 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
1687 paintTrack(g2);
1688 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
1689 paintThumbs(g2);
1690 }
1691
1692 protected abstract void paintTrack(Graphics2D g);
1693
1694 protected abstract void paintFocus(Graphics2D g);
1695
1696 protected abstract void paintThumbs(Graphics2D g);
1697
1698 @Override
1699 public void uninstallUI(JComponent slider)
1700 {
1701 slider.removeMouseListener(this);
1702 slider.removeMouseMotionListener(this);
1703 slider.removeFocusListener(this.focusListener);
1704 slider.removeKeyListener(this.keyListener);
1705 slider.removeComponentListener(this.compListener);
1706 slider.removePropertyChangeListener(this.propertyListener);
1707 slider.removePropertyChangeListener(THUMB_SHAPE_PROPERTY, this.thumbShapeListener);
1708 super.uninstallUI(slider);
1709 }
1710
1711 }