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