~ubuntu-branches/debian/stretch/insubstantial/stretch

« back to all changes in this revision

Viewing changes to substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceScrollBarUI.java

  • Committer: Package Import Robot
  • Author(s): Felix Natter
  • Date: 2016-01-18 20:58:45 UTC
  • Revision ID: package-import@ubuntu.com-20160118205845-crbmrkda61qsi5qa
Tags: upstream-7.3+dfsg2
ImportĀ upstreamĀ versionĀ 7.3+dfsg2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
 
3
 *
 
4
 * Redistribution and use in source and binary forms, with or without
 
5
 * modification, are permitted provided that the following conditions are met:
 
6
 *
 
7
 *  o Redistributions of source code must retain the above copyright notice,
 
8
 *    this list of conditions and the following disclaimer.
 
9
 *
 
10
 *  o Redistributions in binary form must reproduce the above copyright notice,
 
11
 *    this list of conditions and the following disclaimer in the documentation
 
12
 *    and/or other materials provided with the distribution.
 
13
 *
 
14
 *  o Neither the name of Substance Kirill Grouchnikov nor the names of
 
15
 *    its contributors may be used to endorse or promote products derived
 
16
 *    from this software without specific prior written permission.
 
17
 *
 
18
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 
19
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 
20
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 
21
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 
22
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 
23
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 
24
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 
25
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 
26
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 
27
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 
28
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
29
 */
 
30
package org.pushingpixels.substance.internal.ui;
 
31
 
 
32
import java.awt.Adjustable;
 
33
import java.awt.AlphaComposite;
 
34
import java.awt.Color;
 
35
import java.awt.Component;
 
36
import java.awt.Dimension;
 
37
import java.awt.Graphics;
 
38
import java.awt.Graphics2D;
 
39
import java.awt.Insets;
 
40
import java.awt.Rectangle;
 
41
import java.awt.Shape;
 
42
import java.awt.event.AdjustmentEvent;
 
43
import java.awt.event.AdjustmentListener;
 
44
import java.awt.event.MouseAdapter;
 
45
import java.awt.event.MouseEvent;
 
46
import java.awt.event.MouseListener;
 
47
import java.awt.geom.AffineTransform;
 
48
import java.awt.geom.GeneralPath;
 
49
import java.awt.image.BufferedImage;
 
50
import java.beans.PropertyChangeEvent;
 
51
import java.beans.PropertyChangeListener;
 
52
import java.util.EnumSet;
 
53
import java.util.LinkedList;
 
54
import java.util.List;
 
55
import java.util.Map;
 
56
import java.util.Set;
 
57
 
 
58
import javax.swing.AbstractButton;
 
59
import javax.swing.BoundedRangeModel;
 
60
import javax.swing.ButtonModel;
 
61
import javax.swing.DefaultButtonModel;
 
62
import javax.swing.Icon;
 
63
import javax.swing.JButton;
 
64
import javax.swing.JComponent;
 
65
import javax.swing.JScrollBar;
 
66
import javax.swing.JScrollPane;
 
67
import javax.swing.SwingUtilities;
 
68
import javax.swing.event.ChangeEvent;
 
69
import javax.swing.event.ChangeListener;
 
70
import javax.swing.plaf.ComponentUI;
 
71
import javax.swing.plaf.UIResource;
 
72
import javax.swing.plaf.basic.BasicScrollBarUI;
 
73
 
 
74
import org.pushingpixels.lafwidget.LafWidgetUtilities;
 
75
import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
 
76
import org.pushingpixels.substance.api.ComponentState;
 
77
import org.pushingpixels.substance.api.SubstanceColorScheme;
 
78
import org.pushingpixels.substance.api.SubstanceConstants;
 
79
import org.pushingpixels.substance.api.SubstanceLookAndFeel;
 
80
import org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind;
 
81
import org.pushingpixels.substance.api.SubstanceConstants.Side;
 
82
import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
 
83
import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
 
84
import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
 
85
import org.pushingpixels.substance.api.shaper.SubstanceButtonShaper;
 
86
import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
 
87
import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
 
88
import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
 
89
import org.pushingpixels.substance.internal.painter.SimplisticFillPainter;
 
90
import org.pushingpixels.substance.internal.painter.SimplisticSoftBorderPainter;
 
91
import org.pushingpixels.substance.internal.utils.HashMapKey;
 
92
import org.pushingpixels.substance.internal.utils.LazyResettableHashMap;
 
93
import org.pushingpixels.substance.internal.utils.RolloverControlListener;
 
94
import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
 
95
import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
 
96
import org.pushingpixels.substance.internal.utils.SubstanceImageCreator;
 
97
import org.pushingpixels.substance.internal.utils.SubstanceOutlineUtilities;
 
98
import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
 
99
import org.pushingpixels.substance.internal.utils.icon.ArrowButtonTransitionAwareIcon;
 
100
import org.pushingpixels.substance.internal.utils.scroll.SubstanceScrollButton;
 
101
 
 
102
/**
 
103
 * UI for scroll bars in <b>Substance </b> look and feel.
 
104
 * 
 
105
 * @author Kirill Grouchnikov
 
106
 */
 
107
public class SubstanceScrollBarUI extends BasicScrollBarUI implements
 
108
                TransitionAwareUI {
 
109
        /**
 
110
         * The second decrease button. Is shown under
 
111
         * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#ADJACENT},
 
112
         * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#MULTIPLE} and
 
113
         * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#MULTIPLE_BOTH}
 
114
         * modes.
 
115
         * 
 
116
         * @since version 3.1
 
117
         */
 
118
        protected JButton mySecondDecreaseButton;
 
119
 
 
120
        /**
 
121
         * The second increase button. Is shown only under
 
122
         * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#MULTIPLE_BOTH} mode.
 
123
         * 
 
124
         * @since version 3.1
 
125
         */
 
126
        protected JButton mySecondIncreaseButton;
 
127
 
 
128
        /**
 
129
         * Surrogate button model for tracking the thumb transitions.
 
130
         */
 
131
        private ButtonModel thumbModel;
 
132
 
 
133
        /**
 
134
         * Stores computed images for vertical thumbs.
 
135
         */
 
136
        private static LazyResettableHashMap<BufferedImage> thumbVerticalMap = new LazyResettableHashMap<BufferedImage>(
 
137
                        "SubstanceScrollBarUI.thumbVertical");
 
138
 
 
139
        /**
 
140
         * Stores computed images for horizontal thumbs.
 
141
         */
 
142
        private static LazyResettableHashMap<BufferedImage> thumbHorizontalMap = new LazyResettableHashMap<BufferedImage>(
 
143
                        "SubstanceScrollBarUI.thumbHorizontal");
 
144
 
 
145
        /**
 
146
         * Mouse listener on the associated scroll bar.
 
147
         */
 
148
        private MouseListener substanceMouseListener;
 
149
 
 
150
        /**
 
151
         * Listener for thumb transition animations.
 
152
         */
 
153
        private RolloverControlListener substanceThumbRolloverListener;
 
154
 
 
155
        protected StateTransitionTracker compositeStateTransitionTracker;
 
156
 
 
157
        /**
 
158
         * Property change listener.
 
159
         * 
 
160
         */
 
161
        private PropertyChangeListener substancePropertyListener;
 
162
 
 
163
        /**
 
164
         * Scroll bar width.
 
165
         */
 
166
        protected int scrollBarWidth;
 
167
 
 
168
        /**
 
169
         * Cache of images for horizontal tracks.
 
170
         */
 
171
        private static LazyResettableHashMap<BufferedImage> trackHorizontalMap = new LazyResettableHashMap<BufferedImage>(
 
172
                        "SubstanceScrollBarUI.trackHorizontal");
 
173
 
 
174
        /**
 
175
         * Cache of images for vertical tracks.
 
176
         */
 
177
        private static LazyResettableHashMap<BufferedImage> trackVerticalMap = new LazyResettableHashMap<BufferedImage>(
 
178
                        "SubstanceScrollBarUI.trackVertical");
 
179
 
 
180
        /**
 
181
         * Listener on adjustments made to the scrollbar model - this is for
 
182
     * repaiting both scrollbars with the viewport.
 
183
         * 
 
184
         * @since version 3.2
 
185
         */
 
186
        protected AdjustmentListener substanceAdjustmentListener;
 
187
 
 
188
        /**
 
189
         * Surrogate model to sync between rollover effects of scroll buttons and
 
190
         * scroll track / scroll thumb.
 
191
         * 
 
192
         * @since version 3.2
 
193
         */
 
194
        protected CompositeButtonModel compositeScrollTrackModel;
 
195
 
 
196
        /*
 
197
         * (non-Javadoc)
 
198
         * 
 
199
         * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
 
200
         */
 
201
        public static ComponentUI createUI(JComponent comp) {
 
202
                SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
 
203
                return new SubstanceScrollBarUI(comp);
 
204
        }
 
205
 
 
206
        /**
 
207
         * Simple constructor.
 
208
         * 
 
209
         * @param b
 
210
         *            Associated component.
 
211
         */
 
212
        protected SubstanceScrollBarUI(JComponent b) {
 
213
                super();
 
214
                this.thumbModel = new DefaultButtonModel();
 
215
                this.thumbModel.setArmed(false);
 
216
                this.thumbModel.setSelected(false);
 
217
                this.thumbModel.setPressed(false);
 
218
                this.thumbModel.setRollover(false);
 
219
 
 
220
                b.setOpaque(false);
 
221
        }
 
222
 
 
223
        /**
 
224
         * Creates a decrease button.
 
225
         * 
 
226
         * @param orientation
 
227
         *            Button orientation.
 
228
         * @param isRegular
 
229
         *            if <code>true</code>, the regular (upper / left) decrease
 
230
         *            button is created, if <code>false</code>, the additional
 
231
         *            (lower / right) decrease button is created for
 
232
         *            {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#ADJACENT}
 
233
         *            ,
 
234
         *            {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#MULTIPLE}
 
235
         *            and
 
236
         *            {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#MULTIPLE_BOTH}
 
237
         *            kinds.
 
238
         * @return Decrease button.
 
239
         */
 
240
        protected JButton createGeneralDecreaseButton(final int orientation,
 
241
                        boolean isRegular) {
 
242
                JButton result = new SubstanceScrollButton(orientation);
 
243
                result.setName("Decrease " + (isRegular ? "regular" : "additional"));
 
244
                result.setFont(this.scrollbar.getFont());
 
245
                Icon icon = new ArrowButtonTransitionAwareIcon(result, orientation);
 
246
                result.setIcon(icon);
 
247
                result.setFont(scrollbar.getFont());
 
248
 
 
249
                result.setPreferredSize(new Dimension(this.scrollBarWidth,
 
250
                                this.scrollBarWidth));
 
251
 
 
252
                Set<Side> openSides = EnumSet.noneOf(Side.class);
 
253
                Set<Side> straightSides = EnumSet.noneOf(Side.class);
 
254
                switch (orientation) {
 
255
                case NORTH:
 
256
                        openSides.add(Side.BOTTOM);
 
257
                        if (!isRegular)
 
258
                                openSides.add(Side.TOP);
 
259
                        if (isRegular)
 
260
                                straightSides.add(Side.TOP);
 
261
                        break;
 
262
                case EAST:
 
263
                        openSides.add(Side.LEFT);
 
264
                        if (!isRegular)
 
265
                                openSides.add(Side.RIGHT);
 
266
                        if (isRegular)
 
267
                                straightSides.add(Side.RIGHT);
 
268
                        break;
 
269
                case WEST:
 
270
                        openSides.add(Side.RIGHT);
 
271
                        if (!isRegular)
 
272
                                openSides.add(Side.LEFT);
 
273
                        if (isRegular)
 
274
                                straightSides.add(Side.LEFT);
 
275
                        break;
 
276
                }
 
277
                result.putClientProperty(
 
278
                                SubstanceLookAndFeel.BUTTON_OPEN_SIDE_PROPERTY, openSides);
 
279
                result.putClientProperty(SubstanceLookAndFeel.BUTTON_SIDE_PROPERTY,
 
280
                                straightSides);
 
281
 
 
282
                return result;
 
283
        }
 
284
 
 
285
        /*
 
286
         * (non-Javadoc)
 
287
         * 
 
288
         * @see javax.swing.plaf.basic.BasicScrollBarUI#createDecreaseButton(int)
 
289
         */
 
290
        @Override
 
291
        protected JButton createDecreaseButton(int orientation) {
 
292
                return this.createGeneralDecreaseButton(orientation, true);
 
293
        }
 
294
 
 
295
        /*
 
296
         * (non-Javadoc)
 
297
         * 
 
298
         * @see javax.swing.plaf.basic.BasicScrollBarUI#createIncreaseButton(int)
 
299
         */
 
300
        @Override
 
301
        protected JButton createIncreaseButton(int orientation) {
 
302
                return this.createGeneralIncreaseButton(orientation, true);
 
303
        }
 
304
 
 
305
        /**
 
306
         * Creates a increase button.
 
307
         * 
 
308
         * @param orientation
 
309
         *            Button orientation.
 
310
         * @param isRegular
 
311
         *            if <code>true</code>, the regular (lower / right) increase
 
312
         *            button is created, if <code>false</code>, the additional
 
313
         *            (upper / left) increase button is created for
 
314
         *            {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#MULTIPLE_BOTH}
 
315
         *            kind.
 
316
         * @return Increase button.
 
317
         */
 
318
        protected JButton createGeneralIncreaseButton(final int orientation,
 
319
                        boolean isRegular) {
 
320
                JButton result = new SubstanceScrollButton(orientation);
 
321
                result.setName("Increase " + (isRegular ? "regular" : "additional"));
 
322
                result.setFont(this.scrollbar.getFont());
 
323
                Icon icon = new ArrowButtonTransitionAwareIcon(result, orientation);
 
324
                result.setIcon(icon);
 
325
                result.setFont(scrollbar.getFont());
 
326
                // JButton result = new SubstanceScrollBarButton(icon, orientation);
 
327
                result.setPreferredSize(new Dimension(this.scrollBarWidth,
 
328
                                this.scrollBarWidth));
 
329
 
 
330
                Set<Side> openSides = EnumSet.noneOf(Side.class);
 
331
                Set<Side> straightSides = EnumSet.noneOf(Side.class);
 
332
                switch (orientation) {
 
333
                case SOUTH:
 
334
                        openSides.add(Side.TOP);
 
335
                        if (!isRegular)
 
336
                                openSides.add(Side.BOTTOM);
 
337
                        if (isRegular)
 
338
                                straightSides.add(Side.BOTTOM);
 
339
                        break;
 
340
                case EAST:
 
341
                        openSides.add(Side.LEFT);
 
342
                        if (!isRegular)
 
343
                                openSides.add(Side.RIGHT);
 
344
                        if (isRegular)
 
345
                                straightSides.add(Side.RIGHT);
 
346
                        break;
 
347
                case WEST:
 
348
                        openSides.add(Side.RIGHT);
 
349
                        if (!isRegular)
 
350
                                openSides.add(Side.LEFT);
 
351
                        if (isRegular)
 
352
                                straightSides.add(Side.LEFT);
 
353
                        break;
 
354
                }
 
355
                result.putClientProperty(
 
356
                                SubstanceLookAndFeel.BUTTON_OPEN_SIDE_PROPERTY, openSides);
 
357
                result.putClientProperty(SubstanceLookAndFeel.BUTTON_SIDE_PROPERTY,
 
358
                                straightSides);
 
359
                return result;
 
360
        }
 
361
 
 
362
        /**
 
363
         * Returns the image for a horizontal track.
 
364
         * 
 
365
         * @param trackBounds
 
366
         *            Track bounds.
 
367
         * @param leftActiveButton
 
368
         *            The closest left button in the scroll bar. May be
 
369
         *            <code>null</code>.
 
370
         * @param rightActiveButton
 
371
         *            The closest right button in the scroll bar. May be
 
372
         *            <code>null</code> .
 
373
         * @return Horizontal track image.
 
374
         */
 
375
        private void paintTrackHorizontal(Graphics g, Rectangle trackBounds,
 
376
                        SubstanceScrollButton leftActiveButton,
 
377
                        SubstanceScrollButton rightActiveButton) {
 
378
                int width = Math.max(1, trackBounds.width);
 
379
                int height = Math.max(1, trackBounds.height);
 
380
 
 
381
                paintTrackBackHorizontal(g, this.scrollbar, leftActiveButton,
 
382
                                rightActiveButton, width, height);
 
383
                BufferedImage horizontalTrack = getTrackHorizontal(this.scrollbar,
 
384
                                width, height);
 
385
                g.drawImage(horizontalTrack, 0, 0, null);
 
386
        }
 
387
 
 
388
        /**
 
389
         * Returns the image for a horizontal track.
 
390
         * 
 
391
         * @param scrollBar
 
392
         *            Scroll bar.
 
393
         * @param width
 
394
         *            Scroll track width.
 
395
         * @param height
 
396
         *            Scroll track height.
 
397
         * @return Horizontal track image.
 
398
         */
 
399
        private static BufferedImage getTrackHorizontal(JScrollBar scrollBar,
 
400
                        int width, int height) {
 
401
                SubstanceButtonShaper shaper = SubstanceCoreUtilities
 
402
                                .getButtonShaper(scrollBar);
 
403
                SubstanceColorScheme mainScheme = SubstanceColorSchemeUtilities
 
404
                                .getColorScheme(scrollBar,
 
405
                                                scrollBar.isEnabled() ? ComponentState.ENABLED
 
406
                                                                : ComponentState.DISABLED_UNSELECTED);
 
407
                SubstanceColorScheme mainBorderScheme = SubstanceColorSchemeUtilities
 
408
                                .getColorScheme(scrollBar, ColorSchemeAssociationKind.BORDER,
 
409
                                                scrollBar.isEnabled() ? ComponentState.ENABLED
 
410
                                                                : ComponentState.DISABLED_UNSELECTED);
 
411
                HashMapKey key = SubstanceCoreUtilities.getHashKey(mainScheme
 
412
                                .getDisplayName(), mainBorderScheme.getDisplayName(), width,
 
413
                                height, shaper.getDisplayName());
 
414
                float radius = height / 2;
 
415
                if (shaper instanceof ClassicButtonShaper)
 
416
                        radius = SubstanceSizeUtils
 
417
                                        .getClassicButtonCornerRadius(SubstanceSizeUtils
 
418
                                                        .getComponentFontSize(scrollBar));
 
419
 
 
420
                int borderDelta = (int) Math.floor(SubstanceSizeUtils
 
421
                                .getBorderStrokeWidth(SubstanceSizeUtils
 
422
                                                .getComponentFontSize(scrollBar)) / 2.0);
 
423
                Shape contour = SubstanceOutlineUtilities.getBaseOutline(width, height,
 
424
                                radius, null, borderDelta);
 
425
                BufferedImage result = SubstanceScrollBarUI.trackHorizontalMap.get(key);
 
426
                if (result == null) {
 
427
                        result = SubstanceCoreUtilities.getBlankImage(width, height);
 
428
                        SimplisticFillPainter.INSTANCE.paintContourBackground(result
 
429
                                        .createGraphics(), scrollBar, width, height, contour,
 
430
                                        false, mainScheme, true);
 
431
 
 
432
                        SubstanceBorderPainter borderPainter = new SimplisticSoftBorderPainter();
 
433
                        borderPainter.paintBorder(result.getGraphics(), scrollBar, width,
 
434
                                        height, contour, null, mainBorderScheme);
 
435
 
 
436
                        SubstanceScrollBarUI.trackHorizontalMap.put(key, result);
 
437
                }
 
438
                return result;
 
439
        }
 
440
 
 
441
        /**
 
442
         * Returns the image for a horizontal track.
 
443
         * 
 
444
         * @param scrollBar
 
445
         *            Scroll bar.
 
446
         * @param leftActiveButton
 
447
         *            The closest left button in the scroll bar. May be
 
448
         *            <code>null</code>.
 
449
         * @param rightActiveButton
 
450
         *            The closest right button in the scroll bar. May be
 
451
         *            <code>null</code> .
 
452
         * @param width
 
453
         *            Scroll track width.
 
454
         * @param height
 
455
         *            Scroll track height.
 
456
         * @return Horizontal track image.
 
457
         */
 
458
        private static void paintTrackBackHorizontal(Graphics g,
 
459
                        JScrollBar scrollBar, AbstractButton leftActiveButton,
 
460
                        AbstractButton rightActiveButton, int width, int height) {
 
461
                SubstanceButtonShaper shaper = SubstanceCoreUtilities
 
462
                                .getButtonShaper(scrollBar);
 
463
                int radius = height / 2;
 
464
                if (shaper instanceof ClassicButtonShaper)
 
465
                        radius = 2;
 
466
                SubstanceImageCreator.paintCompositeRoundedBackground(scrollBar, g,
 
467
                                width, height, radius, leftActiveButton, rightActiveButton,
 
468
                                false);
 
469
        }
 
470
 
 
471
        /**
 
472
         * Returns the image for a vertical track.
 
473
         * 
 
474
         * @param trackBounds
 
475
         *            Track bounds.
 
476
         * @param topActiveButton
 
477
         *            The closest top button in the scroll bar. May be
 
478
         *            <code>null</code>.
 
479
         * @param bottomActiveButton
 
480
         *            The closest bottom button in the scroll bar. May be
 
481
         *            <code>null</code>.
 
482
         * @return Vertical track image.
 
483
         */
 
484
        private void paintTrackVertical(Graphics g, Rectangle trackBounds,
 
485
                        SubstanceScrollButton topActiveButton,
 
486
                        SubstanceScrollButton bottomActiveButton) {
 
487
 
 
488
                int width = Math.max(1, trackBounds.width);
 
489
                int height = Math.max(1, trackBounds.height);
 
490
 
 
491
                paintTrackBackVertical(g, this.scrollbar, topActiveButton,
 
492
                                bottomActiveButton, width, height);
 
493
                BufferedImage horizontalTrack = getTrackVertical(this.scrollbar, width,
 
494
                                height);
 
495
                g.drawImage(horizontalTrack, 0, 0, null);
 
496
        }
 
497
 
 
498
        /**
 
499
         * Returns the image for a vertical track.
 
500
         * 
 
501
         * @param scrollBar
 
502
         *            Scroll bar.
 
503
         * @param width
 
504
         *            Scroll track width.
 
505
         * @param height
 
506
         *            Scroll track height.
 
507
         * @return Vertical track image.
 
508
         */
 
509
        private static BufferedImage getTrackVertical(JScrollBar scrollBar,
 
510
                        int width, int height) {
 
511
                SubstanceButtonShaper shaper = SubstanceCoreUtilities
 
512
                                .getButtonShaper(scrollBar);
 
513
                SubstanceColorScheme mainScheme = SubstanceColorSchemeUtilities
 
514
                                .getColorScheme(scrollBar,
 
515
                                                scrollBar.isEnabled() ? ComponentState.ENABLED
 
516
                                                                : ComponentState.DISABLED_UNSELECTED);
 
517
                SubstanceColorScheme mainBorderScheme = SubstanceColorSchemeUtilities
 
518
                                .getColorScheme(scrollBar, ColorSchemeAssociationKind.BORDER,
 
519
                                                scrollBar.isEnabled() ? ComponentState.ENABLED
 
520
                                                                : ComponentState.DISABLED_UNSELECTED);
 
521
                HashMapKey key = SubstanceCoreUtilities.getHashKey(mainScheme
 
522
                                .getDisplayName(), mainBorderScheme.getDisplayName(), width,
 
523
                                height, shaper.getDisplayName());
 
524
                BufferedImage result = SubstanceScrollBarUI.trackVerticalMap.get(key);
 
525
                if (result == null) {
 
526
                        float radius = width / 2;
 
527
                        if (shaper instanceof ClassicButtonShaper)
 
528
                                radius = SubstanceSizeUtils
 
529
                                                .getClassicButtonCornerRadius(SubstanceSizeUtils
 
530
                                                                .getComponentFontSize(scrollBar));
 
531
 
 
532
                        int borderDelta = (int) Math.floor(SubstanceSizeUtils
 
533
                                        .getBorderStrokeWidth(SubstanceSizeUtils
 
534
                                                        .getComponentFontSize(scrollBar)) / 2.0);
 
535
                        Shape contour = SubstanceOutlineUtilities.getBaseOutline(height,
 
536
                                        width, radius, null, borderDelta);
 
537
 
 
538
                        result = SubstanceCoreUtilities.getBlankImage(height, width);
 
539
                        SimplisticFillPainter.INSTANCE.paintContourBackground(result
 
540
                                        .createGraphics(), scrollBar, height, width, contour,
 
541
                                        false, mainScheme, true);
 
542
 
 
543
                        SubstanceBorderPainter borderPainter = new SimplisticSoftBorderPainter();
 
544
                        borderPainter.paintBorder(result.getGraphics(), scrollBar, height,
 
545
                                        width, contour, null, mainBorderScheme);
 
546
                        result = SubstanceImageCreator.getRotated(result, 3);
 
547
 
 
548
                        SubstanceScrollBarUI.trackVerticalMap.put(key, result);
 
549
                }
 
550
                return result;
 
551
        }
 
552
 
 
553
        /**
 
554
         * Returns the image for a vertical track.
 
555
         * 
 
556
         * @param scrollBar
 
557
         *            Scroll bar.
 
558
         * @param topActiveButton
 
559
         *            The closest top button in the scroll bar. May be
 
560
         *            <code>null</code>.
 
561
         * @param bottomActiveButton
 
562
         *            The closest bottom button in the scroll bar. May be
 
563
         *            <code>null</code>.
 
564
         * @param width
 
565
         *            Scroll track width.
 
566
         * @param height
 
567
         *            Scroll track height.
 
568
         * @return Vertical track image.
 
569
         */
 
570
        private static void paintTrackBackVertical(Graphics g,
 
571
                        JScrollBar scrollBar, AbstractButton topActiveButton,
 
572
                        AbstractButton bottomActiveButton, int width, int height) {
 
573
                SubstanceButtonShaper shaper = SubstanceCoreUtilities
 
574
                                .getButtonShaper(scrollBar);
 
575
                int radius = width / 2;
 
576
                if (shaper instanceof ClassicButtonShaper)
 
577
                        radius = 2;
 
578
 
 
579
                Graphics2D g2d = (Graphics2D) g.create();
 
580
                AffineTransform at = AffineTransform.getTranslateInstance(0, height);
 
581
                at.rotate(-Math.PI / 2);
 
582
                g2d.transform(at);
 
583
                SubstanceImageCreator.paintCompositeRoundedBackground(scrollBar, g2d,
 
584
                                height, width, radius, topActiveButton, bottomActiveButton,
 
585
                                true);
 
586
                g2d.dispose();
 
587
        }
 
588
 
 
589
        /**
 
590
         * Retrieves image for vertical thumb.
 
591
         * 
 
592
         * @param thumbBounds
 
593
         *            Thumb bounding rectangle.
 
594
         * @return Image for vertical thumb.
 
595
         */
 
596
        private BufferedImage getThumbVertical(Rectangle thumbBounds) {
 
597
                int width = Math.max(1, thumbBounds.width);
 
598
                int height = Math.max(1, thumbBounds.height);
 
599
 
 
600
                StateTransitionTracker.ModelStateInfo modelStateInfo = this.compositeStateTransitionTracker
 
601
                                .getModelStateInfo();
 
602
                ComponentState currState = modelStateInfo.getCurrModelState();
 
603
 
 
604
                // enabled scroll bar is always painted as active
 
605
                SubstanceColorScheme baseFillScheme = (currState != ComponentState.ENABLED) ? SubstanceColorSchemeUtilities
 
606
                                .getColorScheme(this.scrollbar, currState)
 
607
                                : SubstanceColorSchemeUtilities.getActiveColorScheme(
 
608
                                                this.scrollbar, currState);
 
609
                SubstanceColorScheme baseBorderScheme = SubstanceColorSchemeUtilities
 
610
                                .getColorScheme(this.scrollbar,
 
611
                                                ColorSchemeAssociationKind.BORDER, currState);
 
612
                BufferedImage baseLayer = getThumbVertical(this.scrollbar, width,
 
613
                                height, baseFillScheme, baseBorderScheme);
 
614
 
 
615
                Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
 
616
                                .getStateContributionMap();
 
617
                if (currState.isDisabled() || (activeStates.size() == 1)) {
 
618
                        return baseLayer;
 
619
                }
 
620
 
 
621
                BufferedImage result = SubstanceCoreUtilities.getBlankImage(baseLayer
 
622
                                .getWidth(), baseLayer.getHeight());
 
623
                Graphics2D g2d = result.createGraphics();
 
624
                g2d.drawImage(baseLayer, 0, 0, null);
 
625
 
 
626
                for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
 
627
                                .entrySet()) {
 
628
                        ComponentState activeState = activeEntry.getKey();
 
629
                        if (activeState == modelStateInfo.getCurrModelState())
 
630
                                continue;
 
631
 
 
632
                        float contribution = activeEntry.getValue().getContribution();
 
633
                        if (contribution == 0.0f)
 
634
                                continue;
 
635
 
 
636
                        g2d.setComposite(AlphaComposite.SrcOver.derive(contribution));
 
637
 
 
638
                        SubstanceColorScheme fillScheme = (activeState != ComponentState.ENABLED) ? SubstanceColorSchemeUtilities
 
639
                                        .getColorScheme(this.scrollbar, activeState)
 
640
                                        : SubstanceColorSchemeUtilities.getActiveColorScheme(
 
641
                                                        this.scrollbar, activeState);
 
642
                        SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
 
643
                                        .getColorScheme(this.scrollbar,
 
644
                                                        ColorSchemeAssociationKind.BORDER, activeState);
 
645
                        BufferedImage layer = getThumbVertical(this.scrollbar, width,
 
646
                                        height, fillScheme, borderScheme);
 
647
                        g2d.drawImage(layer, 0, 0, null);
 
648
                }
 
649
 
 
650
                g2d.dispose();
 
651
                return result;
 
652
        }
 
653
 
 
654
        /**
 
655
         * Retrieves image for vertical thumb.
 
656
         * 
 
657
         * @param scrollBar
 
658
         *            Scroll bar.
 
659
         * @param width
 
660
         *            Thumb width.
 
661
         * @param height
 
662
         *            Thumb height.
 
663
         * @param scheme
 
664
         *            The first color scheme.
 
665
         * @param borderScheme
 
666
         *            The first border color scheme.
 
667
         * @return Image for vertical thumb.
 
668
         */
 
669
        private static BufferedImage getThumbVertical(JScrollBar scrollBar,
 
670
                        int width, int height, SubstanceColorScheme scheme,
 
671
                        SubstanceColorScheme borderScheme) {
 
672
                SubstanceFillPainter painter = SubstanceCoreUtilities
 
673
                                .getFillPainter(scrollBar);
 
674
                SubstanceButtonShaper shaper = SubstanceCoreUtilities
 
675
                                .getButtonShaper(scrollBar);
 
676
                SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
 
677
                                .getBorderPainter(scrollBar);
 
678
                HashMapKey key = SubstanceCoreUtilities.getHashKey(width, height,
 
679
                                scheme.getDisplayName(), borderScheme.getDisplayName(), painter
 
680
                                                .getDisplayName(), shaper.getDisplayName(),
 
681
                                borderPainter.getDisplayName());
 
682
                BufferedImage result = SubstanceScrollBarUI.thumbVerticalMap.get(key);
 
683
                if (result == null) {
 
684
                        // System.out.println("Cache miss - computing");
 
685
                        // System.out.println("New image for vertical thumb");
 
686
                        float radius = width / 2;
 
687
                        if (shaper instanceof ClassicButtonShaper)
 
688
                                radius = SubstanceSizeUtils
 
689
                                                .getClassicButtonCornerRadius(SubstanceSizeUtils
 
690
                                                                .getComponentFontSize(scrollBar));
 
691
 
 
692
                        int borderDelta = (int) Math.floor(SubstanceSizeUtils
 
693
                                        .getBorderStrokeWidth(SubstanceSizeUtils
 
694
                                                        .getComponentFontSize(scrollBar)) / 2.0);
 
695
                        GeneralPath contour = SubstanceOutlineUtilities.getBaseOutline(
 
696
                                        height, width, radius, null, borderDelta);
 
697
 
 
698
                        result = SubstanceCoreUtilities.getBlankImage(height, width);
 
699
                        painter.paintContourBackground(result.createGraphics(), scrollBar,
 
700
                                        height, width, contour, false, scheme, true);
 
701
 
 
702
                        // int borderThickness = (int) SubstanceSizeUtils
 
703
                        // .getBorderStrokeWidth(SubstanceSizeUtils
 
704
                        // .getComponentFontSize(scrollBar));
 
705
                        // GeneralPath contourInner = SubstanceOutlineUtilities
 
706
                        // .getBaseOutline(height, width, radius, null,
 
707
                        // borderThickness + borderDelta);
 
708
                        borderPainter.paintBorder(result.getGraphics(), scrollBar, height,
 
709
                                        width, contour, null, borderScheme);
 
710
                        result = SubstanceImageCreator.getRotated(result, 3);
 
711
                        // System.out.println(key);
 
712
                        SubstanceScrollBarUI.thumbVerticalMap.put(key, result);
 
713
                }
 
714
 
 
715
                return result;
 
716
        }
 
717
 
 
718
        /**
 
719
         * Retrieves image for horizontal thumb.
 
720
         * 
 
721
         * @param thumbBounds
 
722
         *            Thumb bounding rectangle.
 
723
         * @return Image for horizontal thumb.
 
724
         */
 
725
        private BufferedImage getThumbHorizontal(Rectangle thumbBounds) {
 
726
                int width = Math.max(1, thumbBounds.width);
 
727
                int height = Math.max(1, thumbBounds.height);
 
728
 
 
729
                StateTransitionTracker.ModelStateInfo modelStateInfo = this.compositeStateTransitionTracker
 
730
                                .getModelStateInfo();
 
731
                ComponentState currState = modelStateInfo.getCurrModelState();
 
732
                // enabled scroll bar is always painted as active
 
733
                // if (currState == ComponentState.ENABLED)
 
734
                // currState = ComponentState.SELECTED;
 
735
 
 
736
                SubstanceColorScheme baseFillScheme = (currState != ComponentState.ENABLED) ? SubstanceColorSchemeUtilities
 
737
                                .getColorScheme(this.scrollbar, currState)
 
738
                                : SubstanceColorSchemeUtilities.getActiveColorScheme(
 
739
                                                this.scrollbar, currState);
 
740
                SubstanceColorScheme baseBorderScheme = SubstanceColorSchemeUtilities
 
741
                                .getColorScheme(this.scrollbar,
 
742
                                                ColorSchemeAssociationKind.BORDER, currState);
 
743
                BufferedImage baseLayer = getThumbHorizontal(this.scrollbar, width,
 
744
                                height, baseFillScheme, baseBorderScheme);
 
745
 
 
746
                Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
 
747
                                .getStateContributionMap();
 
748
                if (currState.isDisabled() || (activeStates.size() == 1)) {
 
749
                        return baseLayer;
 
750
                }
 
751
 
 
752
                BufferedImage result = SubstanceCoreUtilities.getBlankImage(baseLayer
 
753
                                .getWidth(), baseLayer.getHeight());
 
754
                Graphics2D g2d = result.createGraphics();
 
755
                g2d.drawImage(baseLayer, 0, 0, null);
 
756
 
 
757
                for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
 
758
                                .entrySet()) {
 
759
                        ComponentState activeState = activeEntry.getKey();
 
760
                        if (activeState == modelStateInfo.getCurrModelState())
 
761
                                continue;
 
762
                        // if (activeState == ComponentState.ENABLED)
 
763
                        // activeState = ComponentState.SELECTED;
 
764
 
 
765
                        float contribution = activeEntry.getValue().getContribution();
 
766
                        if (contribution == 0.0f)
 
767
                                continue;
 
768
 
 
769
                        g2d.setComposite(AlphaComposite.SrcOver.derive(contribution));
 
770
 
 
771
                        SubstanceColorScheme fillScheme = (activeState != ComponentState.ENABLED) ? SubstanceColorSchemeUtilities
 
772
                                        .getColorScheme(this.scrollbar, activeState)
 
773
                                        : SubstanceColorSchemeUtilities.getActiveColorScheme(
 
774
                                                        this.scrollbar, activeState);
 
775
                        SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
 
776
                                        .getColorScheme(this.scrollbar,
 
777
                                                        ColorSchemeAssociationKind.BORDER, activeState);
 
778
                        BufferedImage layer = getThumbHorizontal(this.scrollbar, width,
 
779
                                        height, fillScheme, borderScheme);
 
780
                        g2d.drawImage(layer, 0, 0, null);
 
781
                }
 
782
 
 
783
                g2d.dispose();
 
784
                return result;
 
785
        }
 
786
 
 
787
        /**
 
788
         * Retrieves image for horizontal thumb.
 
789
         * 
 
790
         * @param scrollBar
 
791
         *            Scroll bar.
 
792
         * @param width
 
793
         *            Thumb width.
 
794
         * @param height
 
795
         *            Thumb height.
 
796
         * @param scheme
 
797
         *            The first color scheme.
 
798
         * @param borderScheme
 
799
         *            The first border color scheme.
 
800
         * @return Image for horizontal thumb.
 
801
         */
 
802
        private static BufferedImage getThumbHorizontal(JScrollBar scrollBar,
 
803
                        int width, int height, SubstanceColorScheme scheme,
 
804
                        SubstanceColorScheme borderScheme) {
 
805
                SubstanceFillPainter painter = SubstanceCoreUtilities
 
806
                                .getFillPainter(scrollBar);
 
807
                SubstanceButtonShaper shaper = SubstanceCoreUtilities
 
808
                                .getButtonShaper(scrollBar);
 
809
                SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
 
810
                                .getBorderPainter(scrollBar);
 
811
                HashMapKey key = SubstanceCoreUtilities.getHashKey(width, height,
 
812
                                scheme.getDisplayName(), borderScheme.getDisplayName(), painter
 
813
                                                .getDisplayName(), shaper.getDisplayName(),
 
814
                                borderPainter.getDisplayName());
 
815
 
 
816
                float radius = height / 2;
 
817
                if (shaper instanceof ClassicButtonShaper)
 
818
                        radius = SubstanceSizeUtils
 
819
                                        .getClassicButtonCornerRadius(SubstanceSizeUtils
 
820
                                                        .getComponentFontSize(scrollBar));
 
821
                int borderDelta = (int) Math.floor(SubstanceSizeUtils
 
822
                                .getBorderStrokeWidth(SubstanceSizeUtils
 
823
                                                .getComponentFontSize(scrollBar)) / 2.0);
 
824
                GeneralPath contour = SubstanceOutlineUtilities.getBaseOutline(width,
 
825
                                height, radius, null, borderDelta);
 
826
                BufferedImage opaque = SubstanceScrollBarUI.thumbHorizontalMap.get(key);
 
827
                if (opaque == null) {
 
828
                        // System.out.println("New image for horizontal thumb");
 
829
 
 
830
                        opaque = SubstanceCoreUtilities.getBlankImage(width, height);
 
831
                        painter.paintContourBackground(opaque.createGraphics(), scrollBar,
 
832
                                        width, height, contour, false, scheme, true);
 
833
 
 
834
                        borderPainter.paintBorder(opaque.getGraphics(), scrollBar, width,
 
835
                                        height, contour, null, borderScheme);
 
836
                        SubstanceScrollBarUI.thumbHorizontalMap.put(key, opaque);
 
837
                }
 
838
 
 
839
                return opaque;
 
840
        }
 
841
 
 
842
        /**
 
843
         * Returns the scroll button state.
 
844
         * 
 
845
         * @param scrollButton
 
846
         *            Scroll button.
 
847
         * @return Scroll button state.
 
848
         */
 
849
        protected ComponentState getState(JButton scrollButton) {
 
850
                if (scrollButton == null)
 
851
                        return null;
 
852
 
 
853
                ComponentState result = ((TransitionAwareUI) scrollButton.getUI())
 
854
                                .getTransitionTracker().getModelStateInfo().getCurrModelState();
 
855
                if ((result == ComponentState.ENABLED)
 
856
                                && SubstanceCoreUtilities.hasFlatAppearance(this.scrollbar,
 
857
                                                false)) {
 
858
                        result = null;
 
859
                }
 
860
                if (SubstanceCoreUtilities.isButtonNeverPainted(scrollButton)) {
 
861
                        result = null;
 
862
                }
 
863
                return result;
 
864
        }
 
865
 
 
866
        /*
 
867
         * (non-Javadoc)
 
868
         * 
 
869
         * @see
 
870
         * javax.swing.plaf.basic.BasicScrollBarUI#paintTrack(java.awt.Graphics,
 
871
         * javax.swing.JComponent, java.awt.Rectangle)
 
872
         */
 
873
        @Override
 
874
        protected void paintTrack(Graphics g, JComponent c, Rectangle trackBounds) {
 
875
                Graphics2D graphics = (Graphics2D) g.create();
 
876
 
 
877
                // System.out.println("Track");
 
878
                ScrollPaneButtonPolicyKind buttonPolicy = SubstanceCoreUtilities
 
879
                                .getScrollPaneButtonsPolicyKind(this.scrollbar);
 
880
                SubstanceScrollButton compTopState = null;
 
881
                SubstanceScrollButton compBottomState = null;
 
882
                if (this.decrButton.isShowing() && this.incrButton.isShowing()
 
883
                                && this.mySecondDecreaseButton.isShowing()
 
884
                                && this.mySecondIncreaseButton.isShowing()) {
 
885
                        switch (buttonPolicy) {
 
886
                        case OPPOSITE:
 
887
                                compTopState = (SubstanceScrollButton) this.decrButton;
 
888
                                compBottomState = (SubstanceScrollButton) this.incrButton;
 
889
                                break;
 
890
                        case ADJACENT:
 
891
                                compBottomState = (SubstanceScrollButton) this.mySecondDecreaseButton;
 
892
                                break;
 
893
                        case MULTIPLE:
 
894
                                compTopState = (SubstanceScrollButton) this.decrButton;
 
895
                                compBottomState = (SubstanceScrollButton) this.mySecondDecreaseButton;
 
896
                                break;
 
897
                        case MULTIPLE_BOTH:
 
898
                                compTopState = (SubstanceScrollButton) this.mySecondIncreaseButton;
 
899
                                compBottomState = (SubstanceScrollButton) this.mySecondDecreaseButton;
 
900
                                break;
 
901
                        }
 
902
                }
 
903
 
 
904
                graphics.translate(trackBounds.x, trackBounds.y);
 
905
                if (this.scrollbar.getOrientation() == Adjustable.VERTICAL) {
 
906
                        paintTrackVertical(graphics, trackBounds, compTopState,
 
907
                                        compBottomState);
 
908
                } else {
 
909
                        if (this.scrollbar.getComponentOrientation().isLeftToRight()) {
 
910
                                paintTrackHorizontal(graphics, trackBounds, compTopState,
 
911
                                                compBottomState);
 
912
                        } else {
 
913
                                paintTrackHorizontal(graphics, trackBounds, compBottomState,
 
914
                                                compTopState);
 
915
                        }
 
916
                        // BufferedImage bi = this.scrollbar.getComponentOrientation()
 
917
                        // .isLeftToRight() ? this.getTrackHorizontal(trackBounds,
 
918
                        // compTopState, compBottomState) : this.getTrackHorizontal(
 
919
                        // trackBounds, compBottomState, compTopState);
 
920
                        // graphics.drawImage(bi, trackBounds.x, trackBounds.y, null);
 
921
                }
 
922
 
 
923
                graphics.dispose();
 
924
        }
 
925
 
 
926
        /*
 
927
         * (non-Javadoc)
 
928
         * 
 
929
         * @see
 
930
         * javax.swing.plaf.basic.BasicScrollBarUI#paintThumb(java.awt.Graphics,
 
931
         * javax.swing.JComponent, java.awt.Rectangle)
 
932
         */
 
933
        @Override
 
934
        protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds) {
 
935
                // System.out.println("Thumb");
 
936
                Graphics2D graphics = (Graphics2D) g.create();
 
937
                // ControlBackgroundComposite composite = SubstanceCoreUtilities
 
938
                // .getControlBackgroundComposite(this.scrollbar);
 
939
 
 
940
                // JScrollBar scrollBar = (JScrollBar) c;
 
941
                this.thumbModel.setSelected(this.thumbModel.isSelected()
 
942
                                || this.isDragging);
 
943
                this.thumbModel.setEnabled(c.isEnabled());
 
944
                boolean isVertical = (this.scrollbar.getOrientation() == Adjustable.VERTICAL);
 
945
                if (isVertical) {
 
946
                        Rectangle adjustedBounds = new Rectangle(thumbBounds.x,
 
947
                                        thumbBounds.y, thumbBounds.width, thumbBounds.height);
 
948
                        BufferedImage thumbImage = this.getThumbVertical(adjustedBounds);
 
949
                        graphics.drawImage(thumbImage, adjustedBounds.x, adjustedBounds.y,
 
950
                                        null);
 
951
                } else {
 
952
                        Rectangle adjustedBounds = new Rectangle(thumbBounds.x,
 
953
                                        thumbBounds.y, thumbBounds.width, thumbBounds.height);
 
954
                        BufferedImage thumbImage = this.getThumbHorizontal(adjustedBounds);
 
955
                        graphics.drawImage(thumbImage, adjustedBounds.x, adjustedBounds.y,
 
956
                                        null);
 
957
                }
 
958
                graphics.dispose();
 
959
        }
 
960
 
 
961
        @Override
 
962
        public void paint(Graphics g, JComponent c) {
 
963
                Graphics2D graphics = (Graphics2D) g.create();
 
964
                BackgroundPaintingUtils.update(graphics, c, false);
 
965
                float alpha = SubstanceColorSchemeUtilities.getAlpha(this.scrollbar,
 
966
                                ComponentState.getState(this.thumbModel, this.scrollbar));
 
967
                graphics
 
968
                                .setComposite(LafWidgetUtilities.getAlphaComposite(c, alpha, g));
 
969
                super.paint(graphics, c);
 
970
                graphics.dispose();
 
971
        }
 
972
 
 
973
        /*
 
974
         * (non-Javadoc)
 
975
         * 
 
976
         * @see javax.swing.plaf.basic.BasicScrollBarUI#installDefaults()
 
977
         */
 
978
        @Override
 
979
        protected void installDefaults() {
 
980
                super.installDefaults();
 
981
                this.scrollBarWidth = SubstanceSizeUtils
 
982
                                .getScrollBarWidth(SubstanceSizeUtils
 
983
                                                .getComponentFontSize(this.scrollbar));
 
984
        }
 
985
 
 
986
        /*
 
987
         * (non-Javadoc)
 
988
         * 
 
989
         * @see javax.swing.plaf.basic.BasicScrollBarUI#installComponents()
 
990
         */
 
991
        @Override
 
992
        protected void installComponents() {
 
993
                super.installComponents();
 
994
                switch (this.scrollbar.getOrientation()) {
 
995
                case JScrollBar.VERTICAL:
 
996
                        this.mySecondDecreaseButton = this.createGeneralDecreaseButton(
 
997
                                        NORTH, false);
 
998
                        this.mySecondIncreaseButton = this.createGeneralIncreaseButton(
 
999
                                        SOUTH, false);
 
1000
                        break;
 
1001
 
 
1002
                case JScrollBar.HORIZONTAL:
 
1003
                        if (this.scrollbar.getComponentOrientation().isLeftToRight()) {
 
1004
                                this.mySecondDecreaseButton = this.createGeneralDecreaseButton(
 
1005
                                                WEST, false);
 
1006
                                this.mySecondIncreaseButton = this.createGeneralIncreaseButton(
 
1007
                                                EAST, false);
 
1008
                        } else {
 
1009
                                this.mySecondDecreaseButton = this.createGeneralDecreaseButton(
 
1010
                                                EAST, false);
 
1011
                                this.mySecondIncreaseButton = this.createGeneralIncreaseButton(
 
1012
                                                WEST, false);
 
1013
                        }
 
1014
                        break;
 
1015
                }
 
1016
                this.scrollbar.add(this.mySecondDecreaseButton);
 
1017
                this.scrollbar.add(this.mySecondIncreaseButton);
 
1018
 
 
1019
                this.compositeScrollTrackModel = new CompositeButtonModel(
 
1020
                                this.thumbModel, this.incrButton, this.decrButton,
 
1021
                                this.mySecondDecreaseButton, this.mySecondIncreaseButton);
 
1022
                this.compositeScrollTrackModel.registerListeners();
 
1023
 
 
1024
                this.compositeStateTransitionTracker = new StateTransitionTracker(
 
1025
                                this.scrollbar, this.compositeScrollTrackModel);
 
1026
                this.compositeStateTransitionTracker.registerModelListeners();
 
1027
        }
 
1028
 
 
1029
        /*
 
1030
         * (non-Javadoc)
 
1031
         * 
 
1032
         * @see javax.swing.plaf.basic.BasicScrollBarUI#uninstallComponents()
 
1033
         */
 
1034
        @Override
 
1035
        protected void uninstallComponents() {
 
1036
                this.compositeScrollTrackModel.unregisterListeners();
 
1037
                this.compositeStateTransitionTracker.unregisterModelListeners();
 
1038
 
 
1039
                this.scrollbar.remove(this.mySecondDecreaseButton);
 
1040
                this.scrollbar.remove(this.mySecondIncreaseButton);
 
1041
                super.uninstallComponents();
 
1042
        }
 
1043
 
 
1044
        /*
 
1045
         * (non-Javadoc)
 
1046
         * 
 
1047
         * @see javax.swing.plaf.basic.BasicScrollBarUI#installListeners()
 
1048
         */
 
1049
        @Override
 
1050
        protected void installListeners() {
 
1051
                super.installListeners();
 
1052
                this.substanceMouseListener = new MouseAdapter() {
 
1053
                        @Override
 
1054
                        public void mouseEntered(MouseEvent e) {
 
1055
                                SubstanceScrollBarUI.this.scrollbar.repaint();
 
1056
                        }
 
1057
 
 
1058
                        @Override
 
1059
                        public void mouseExited(MouseEvent e) {
 
1060
                                SubstanceScrollBarUI.this.scrollbar.repaint();
 
1061
                        }
 
1062
 
 
1063
                        @Override
 
1064
                        public void mousePressed(MouseEvent e) {
 
1065
                                SubstanceScrollBarUI.this.scrollbar.repaint();
 
1066
                        }
 
1067
 
 
1068
                        @Override
 
1069
                        public void mouseReleased(MouseEvent e) {
 
1070
                                SubstanceScrollBarUI.this.scrollbar.repaint();
 
1071
                        }
 
1072
                };
 
1073
 
 
1074
                this.incrButton.addMouseListener(this.substanceMouseListener);
 
1075
                this.decrButton.addMouseListener(this.substanceMouseListener);
 
1076
                this.mySecondDecreaseButton
 
1077
                                .addMouseListener(this.substanceMouseListener);
 
1078
                this.mySecondIncreaseButton
 
1079
                                .addMouseListener(this.substanceMouseListener);
 
1080
 
 
1081
                this.substanceThumbRolloverListener = new RolloverControlListener(this,
 
1082
                                this.thumbModel);
 
1083
                this.scrollbar.addMouseListener(this.substanceThumbRolloverListener);
 
1084
                this.scrollbar
 
1085
                                .addMouseMotionListener(this.substanceThumbRolloverListener);
 
1086
 
 
1087
                // this.thumbStateTransitionTracker.registerModelListeners();
 
1088
 
 
1089
                this.substancePropertyListener = new PropertyChangeListener() {
 
1090
                        @Override
 
1091
            public void propertyChange(PropertyChangeEvent evt) {
 
1092
                                if ("font".equals(evt.getPropertyName())) {
 
1093
                                        SwingUtilities.invokeLater(new Runnable() {
 
1094
                                                @Override
 
1095
                                                public void run() {
 
1096
              if ( scrollbar != null ) scrollbar.updateUI();
 
1097
                                                }
 
1098
                                        });
 
1099
                                }
 
1100
                                if ("background".equals(evt.getPropertyName())) {
 
1101
                                        // propagate application-specific background color to the
 
1102
                                        // scroll buttons.
 
1103
                                        Color newBackgr = (Color) evt.getNewValue();
 
1104
                                        if (!(newBackgr instanceof UIResource)) {
 
1105
                                                if (mySecondDecreaseButton != null) {
 
1106
                                                        if (mySecondDecreaseButton.getBackground() instanceof UIResource) {
 
1107
                                                                mySecondDecreaseButton.setBackground(newBackgr);
 
1108
                                                        }
 
1109
                                                }
 
1110
                                                if (mySecondIncreaseButton != null) {
 
1111
                                                        if (mySecondIncreaseButton.getBackground() instanceof UIResource) {
 
1112
                                                                mySecondIncreaseButton.setBackground(newBackgr);
 
1113
                                                        }
 
1114
                                                }
 
1115
                                                if (incrButton != null) {
 
1116
                                                        if (incrButton.getBackground() instanceof UIResource) {
 
1117
                                                                incrButton.setBackground(newBackgr);
 
1118
                                                        }
 
1119
                                                }
 
1120
                                                if (decrButton != null) {
 
1121
                                                        if (decrButton.getBackground() instanceof UIResource) {
 
1122
                                                                decrButton.setBackground(newBackgr);
 
1123
                                                        }
 
1124
                                                }
 
1125
                                        }
 
1126
                                }
 
1127
                        }
 
1128
                };
 
1129
                this.scrollbar
 
1130
                                .addPropertyChangeListener(this.substancePropertyListener);
 
1131
 
 
1132
                this.mySecondDecreaseButton.addMouseListener(this.buttonListener);
 
1133
                this.mySecondIncreaseButton.addMouseListener(this.buttonListener);
 
1134
 
 
1135
                this.substanceAdjustmentListener = new AdjustmentListener() {
 
1136
                        @Override
 
1137
            public void adjustmentValueChanged(AdjustmentEvent e) {
 
1138
                                SubstanceCoreUtilities
 
1139
                                                .testComponentStateChangeThreadingViolation(scrollbar);
 
1140
                                Component parent = SubstanceScrollBarUI.this.scrollbar
 
1141
                                                .getParent();
 
1142
                                if (parent instanceof JScrollPane) {
 
1143
                                        JScrollPane jsp = (JScrollPane) parent;
 
1144
                                        JScrollBar hor = jsp.getHorizontalScrollBar();
 
1145
                                        JScrollBar ver = jsp.getVerticalScrollBar();
 
1146
 
 
1147
                                        JScrollBar other = null;
 
1148
                                        if (SubstanceScrollBarUI.this.scrollbar == hor) {
 
1149
                                                other = ver;
 
1150
                                        }
 
1151
                                        if (SubstanceScrollBarUI.this.scrollbar == ver) {
 
1152
                                                other = hor;
 
1153
                                        }
 
1154
 
 
1155
                                        if ((other != null) && other.isVisible())
 
1156
                                                other.repaint();
 
1157
                                        SubstanceScrollBarUI.this.scrollbar.repaint();
 
1158
                                }
 
1159
                        }
 
1160
                };
 
1161
                this.scrollbar.addAdjustmentListener(this.substanceAdjustmentListener);
 
1162
        }
 
1163
 
 
1164
        /*
 
1165
         * (non-Javadoc)
 
1166
         * 
 
1167
         * @see javax.swing.plaf.basic.BasicScrollBarUI#uninstallListeners()
 
1168
         */
 
1169
        @Override
 
1170
        protected void uninstallListeners() {
 
1171
                // fix for defect 109 - memory leak on changing skin
 
1172
                this.incrButton.removeMouseListener(this.substanceMouseListener);
 
1173
                this.decrButton.removeMouseListener(this.substanceMouseListener);
 
1174
                this.mySecondDecreaseButton
 
1175
                                .removeMouseListener(this.substanceMouseListener);
 
1176
                this.mySecondIncreaseButton
 
1177
                                .removeMouseListener(this.substanceMouseListener);
 
1178
                this.substanceMouseListener = null;
 
1179
 
 
1180
                this.scrollbar.removeMouseListener(this.substanceThumbRolloverListener);
 
1181
                this.scrollbar
 
1182
                                .removeMouseMotionListener(this.substanceThumbRolloverListener);
 
1183
                this.substanceThumbRolloverListener = null;
 
1184
 
 
1185
                this.scrollbar
 
1186
                                .removePropertyChangeListener(this.substancePropertyListener);
 
1187
                this.substancePropertyListener = null;
 
1188
 
 
1189
                this.mySecondDecreaseButton.removeMouseListener(this.buttonListener);
 
1190
                this.mySecondIncreaseButton.removeMouseListener(this.buttonListener);
 
1191
 
 
1192
                this.scrollbar
 
1193
                                .removeAdjustmentListener(this.substanceAdjustmentListener);
 
1194
                this.substanceAdjustmentListener = null;
 
1195
 
 
1196
                super.uninstallListeners();
 
1197
        }
 
1198
 
 
1199
        @Override
 
1200
    public boolean isInside(MouseEvent me) {
 
1201
                Rectangle trackB = this.getTrackBounds();
 
1202
                if (trackB == null)
 
1203
                        return false;
 
1204
                return trackB.contains(me.getX(), me.getY());
 
1205
        }
 
1206
 
 
1207
        @Override
 
1208
        public StateTransitionTracker getTransitionTracker() {
 
1209
                return this.compositeStateTransitionTracker;
 
1210
        }
 
1211
 
 
1212
        /*
 
1213
         * (non-Javadoc)
 
1214
         * 
 
1215
         * @see javax.swing.plaf.basic.BasicScrollBarUI#scrollByBlock(int)
 
1216
         */
 
1217
        @Override
 
1218
        public void scrollByBlock(int direction) {
 
1219
                // This method is called from SubstanceScrollPaneUI to implement wheel
 
1220
                // scrolling.
 
1221
                int oldValue = this.scrollbar.getValue();
 
1222
                int blockIncrement = this.scrollbar.getBlockIncrement(direction);
 
1223
                int delta = blockIncrement * ((direction > 0) ? +1 : -1);
 
1224
                int newValue = oldValue + delta;
 
1225
 
 
1226
                // Check for overflow.
 
1227
                if ((delta > 0) && (newValue < oldValue)) {
 
1228
                        newValue = this.scrollbar.getMaximum();
 
1229
                } else if ((delta < 0) && (newValue > oldValue)) {
 
1230
                        newValue = this.scrollbar.getMinimum();
 
1231
                }
 
1232
 
 
1233
                this.scrollbar.setValue(newValue);
 
1234
        }
 
1235
 
 
1236
        /**
 
1237
         * Scrolls the associated scroll bar.
 
1238
         * 
 
1239
         * @param direction
 
1240
         *            Direction.
 
1241
         * @param units
 
1242
         *            Scroll units.
 
1243
         */
 
1244
        public void scrollByUnits(int direction, int units) {
 
1245
                // This method is called from SubstanceScrollPaneUI to implement wheel
 
1246
                // scrolling.
 
1247
                int delta;
 
1248
 
 
1249
                for (int i = 0; i < units; i++) {
 
1250
                        if (direction > 0) {
 
1251
                                delta = this.scrollbar.getUnitIncrement(direction);
 
1252
                        } else {
 
1253
                                delta = -this.scrollbar.getUnitIncrement(direction);
 
1254
                        }
 
1255
 
 
1256
                        int oldValue = this.scrollbar.getValue();
 
1257
                        int newValue = oldValue + delta;
 
1258
 
 
1259
                        // Check for overflow.
 
1260
                        if ((delta > 0) && (newValue < oldValue)) {
 
1261
                                newValue = this.scrollbar.getMaximum();
 
1262
                        } else if ((delta < 0) && (newValue > oldValue)) {
 
1263
                                newValue = this.scrollbar.getMinimum();
 
1264
                        }
 
1265
                        if (oldValue == newValue) {
 
1266
                                break;
 
1267
                        }
 
1268
                        this.scrollbar.setValue(newValue);
 
1269
                }
 
1270
        }
 
1271
 
 
1272
        /*
 
1273
         * (non-Javadoc)
 
1274
         * 
 
1275
         * @see
 
1276
         * javax.swing.plaf.basic.BasicScrollBarUI#layoutVScrollbar(javax.swing.
 
1277
         * JScrollBar)
 
1278
         */
 
1279
        @Override
 
1280
        protected void layoutVScrollbar(JScrollBar sb) {
 
1281
                ScrollPaneButtonPolicyKind buttonPolicy = SubstanceCoreUtilities
 
1282
                                .getScrollPaneButtonsPolicyKind(this.scrollbar);
 
1283
                this.mySecondDecreaseButton.setBounds(0, 0, 0, 0);
 
1284
                this.mySecondIncreaseButton.setBounds(0, 0, 0, 0);
 
1285
                switch (buttonPolicy) {
 
1286
                case OPPOSITE:
 
1287
                        super.layoutVScrollbar(sb);
 
1288
                        break;
 
1289
                case NONE:
 
1290
                        this.layoutVScrollbarNone(sb);
 
1291
                        break;
 
1292
                case ADJACENT:
 
1293
                        this.layoutVScrollbarAdjacent(sb);
 
1294
                        break;
 
1295
                case MULTIPLE:
 
1296
                        this.layoutVScrollbarMultiple(sb);
 
1297
                        break;
 
1298
                case MULTIPLE_BOTH:
 
1299
                        this.layoutVScrollbarMultipleBoth(sb);
 
1300
                        break;
 
1301
                }
 
1302
        }
 
1303
 
 
1304
        /*
 
1305
         * (non-Javadoc)
 
1306
         * 
 
1307
         * @see
 
1308
         * javax.swing.plaf.basic.BasicScrollBarUI#layoutHScrollbar(javax.swing.
 
1309
         * JScrollBar)
 
1310
         */
 
1311
        @Override
 
1312
        protected void layoutHScrollbar(JScrollBar sb) {
 
1313
                this.mySecondDecreaseButton.setBounds(0, 0, 0, 0);
 
1314
                this.mySecondIncreaseButton.setBounds(0, 0, 0, 0);
 
1315
                ScrollPaneButtonPolicyKind buttonPolicy = SubstanceCoreUtilities
 
1316
                                .getScrollPaneButtonsPolicyKind(this.scrollbar);
 
1317
                switch (buttonPolicy) {
 
1318
                case OPPOSITE:
 
1319
                        super.layoutHScrollbar(sb);
 
1320
                        break;
 
1321
                case NONE:
 
1322
                        this.layoutHScrollbarNone(sb);
 
1323
                        break;
 
1324
                case ADJACENT:
 
1325
                        this.layoutHScrollbarAdjacent(sb);
 
1326
                        break;
 
1327
                case MULTIPLE:
 
1328
                        this.layoutHScrollbarMultiple(sb);
 
1329
                        break;
 
1330
                case MULTIPLE_BOTH:
 
1331
                        this.layoutHScrollbarMultipleBoth(sb);
 
1332
                        break;
 
1333
                }
 
1334
        }
 
1335
 
 
1336
        /**
 
1337
         * Lays out the vertical scroll bar when the button policy is
 
1338
         * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#ADJACENT}.
 
1339
         * 
 
1340
         * @param sb
 
1341
         *            Scroll bar.
 
1342
         */
 
1343
        protected void layoutVScrollbarAdjacent(JScrollBar sb) {
 
1344
                Dimension sbSize = sb.getSize();
 
1345
                Insets sbInsets = sb.getInsets();
 
1346
 
 
1347
                /*
 
1348
                 * Width and left edge of the buttons and thumb.
 
1349
                 */
 
1350
                int itemW = sbSize.width - (sbInsets.left + sbInsets.right);
 
1351
                int itemX = sbInsets.left;
 
1352
 
 
1353
                /*
 
1354
                 * Nominal locations of the buttons, assuming their preferred size will
 
1355
                 * fit.
 
1356
                 */
 
1357
                int incrButtonH = itemW;
 
1358
                int incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH);
 
1359
 
 
1360
                int decrButton2H = itemW;
 
1361
                int decrButton2Y = incrButtonY - decrButton2H;
 
1362
 
 
1363
                /*
 
1364
                 * The thumb must fit within the height left over after we subtract the
 
1365
                 * preferredSize of the buttons and the insets.
 
1366
                 */
 
1367
                int sbInsetsH = sbInsets.top + sbInsets.bottom;
 
1368
                int sbButtonsH = decrButton2H + incrButtonH;
 
1369
                float trackH = sbSize.height - (sbInsetsH + sbButtonsH);
 
1370
 
 
1371
                /*
 
1372
                 * Compute the height and origin of the thumb. The case where the thumb
 
1373
                 * is at the bottom edge is handled specially to avoid numerical
 
1374
                 * problems in computing thumbY. Enforce the thumbs min/max dimensions.
 
1375
                 * If the thumb doesn't fit in the track (trackH) we'll hide it later.
 
1376
                 */
 
1377
                float min = sb.getMinimum();
 
1378
                float extent = sb.getVisibleAmount();
 
1379
                float range = sb.getMaximum() - min;
 
1380
                float value = sb.getValue();
 
1381
 
 
1382
                int thumbH = (range <= 0) ? this.getMaximumThumbSize().height
 
1383
                                : (int) (trackH * (extent / range));
 
1384
                thumbH = Math.max(thumbH, this.getMinimumThumbSize().height);
 
1385
                thumbH = Math.min(thumbH, this.getMaximumThumbSize().height);
 
1386
 
 
1387
                int thumbY = decrButton2Y - thumbH;
 
1388
                if (value < (sb.getMaximum() - sb.getVisibleAmount())) {
 
1389
                        float thumbRange = trackH - thumbH;
 
1390
                        thumbY = (int) (0.5f + (thumbRange * ((value - min) / (range - extent))));
 
1391
                }
 
1392
 
 
1393
                /*
 
1394
                 * If the buttons don't fit, allocate half of the available space to
 
1395
                 * each and move the lower one (incrButton) down.
 
1396
                 */
 
1397
                int sbAvailButtonH = (sbSize.height - sbInsetsH);
 
1398
                if (sbAvailButtonH < sbButtonsH) {
 
1399
                        incrButtonH = decrButton2H = sbAvailButtonH / 2;
 
1400
                        incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH);
 
1401
                }
 
1402
                this.mySecondIncreaseButton.setBounds(0, 0, 0, 0);
 
1403
                this.decrButton.setBounds(0, 0, 0, 0);
 
1404
                this.mySecondDecreaseButton.setBounds(itemX,
 
1405
                                incrButtonY - decrButton2H, itemW, decrButton2H);
 
1406
                this.incrButton.setBounds(itemX, incrButtonY - 1, itemW,
 
1407
                                incrButtonH + 1);
 
1408
 
 
1409
                /*
 
1410
                 * Update the trackRect field.
 
1411
                 */
 
1412
                int itrackY = 0;
 
1413
                int itrackH = decrButton2Y - itrackY;
 
1414
                this.trackRect.setBounds(itemX, itrackY, itemW, itrackH);
 
1415
 
 
1416
                /*
 
1417
                 * If the thumb isn't going to fit, zero it's bounds. Otherwise make
 
1418
                 * sure it fits between the buttons. Note that setting the thumbs bounds
 
1419
                 * will cause a repaint.
 
1420
                 */
 
1421
                if (thumbH >= (int) trackH) {
 
1422
                        this.setThumbBounds(0, 0, 0, 0);
 
1423
                } else {
 
1424
                        if ((thumbY + thumbH) > decrButton2Y) {
 
1425
                                thumbY = decrButton2Y - thumbH;
 
1426
                        }
 
1427
                        if (thumbY < 0) {
 
1428
                                thumbY = 0;
 
1429
                        }
 
1430
                        this.setThumbBounds(itemX, thumbY, itemW, thumbH);
 
1431
                }
 
1432
        }
 
1433
 
 
1434
        /**
 
1435
         * Lays out the vertical scroll bar when the button policy is
 
1436
         * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#ADJACENT}.
 
1437
         * 
 
1438
         * @param sb
 
1439
         *            Scroll bar.
 
1440
         */
 
1441
        protected void layoutVScrollbarNone(JScrollBar sb) {
 
1442
                Dimension sbSize = sb.getSize();
 
1443
                Insets sbInsets = sb.getInsets();
 
1444
 
 
1445
                /*
 
1446
                 * Width and left edge of the buttons and thumb.
 
1447
                 */
 
1448
                int itemW = sbSize.width - (sbInsets.left + sbInsets.right);
 
1449
                int itemX = sbInsets.left;
 
1450
 
 
1451
                /*
 
1452
                 * Nominal locations of the buttons, assuming their preferred size will
 
1453
                 * fit.
 
1454
                 */
 
1455
                int incrButtonH = 0;
 
1456
                int incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH);
 
1457
 
 
1458
                int decrButton2H = 0;
 
1459
                int decrButton2Y = incrButtonY - decrButton2H;
 
1460
 
 
1461
                /*
 
1462
                 * The thumb must fit within the height left over after we subtract the
 
1463
                 * preferredSize of the buttons and the insets.
 
1464
                 */
 
1465
                int sbInsetsH = sbInsets.top + sbInsets.bottom;
 
1466
                int sbButtonsH = decrButton2H + incrButtonH;
 
1467
                float trackH = sbSize.height - (sbInsetsH + sbButtonsH);
 
1468
 
 
1469
                /*
 
1470
                 * Compute the height and origin of the thumb. The case where the thumb
 
1471
                 * is at the bottom edge is handled specially to avoid numerical
 
1472
                 * problems in computing thumbY. Enforce the thumbs min/max dimensions.
 
1473
                 * If the thumb doesn't fit in the track (trackH) we'll hide it later.
 
1474
                 */
 
1475
                float min = sb.getMinimum();
 
1476
                float extent = sb.getVisibleAmount();
 
1477
                float range = sb.getMaximum() - min;
 
1478
                float value = sb.getValue();
 
1479
 
 
1480
                int thumbH = (range <= 0) ? this.getMaximumThumbSize().height
 
1481
                                : (int) (trackH * (extent / range));
 
1482
                thumbH = Math.max(thumbH, this.getMinimumThumbSize().height);
 
1483
                thumbH = Math.min(thumbH, this.getMaximumThumbSize().height);
 
1484
 
 
1485
                int thumbY = decrButton2Y - thumbH;
 
1486
                if (value < (sb.getMaximum() - sb.getVisibleAmount())) {
 
1487
                        float thumbRange = trackH - thumbH;
 
1488
                        thumbY = (int) (0.5f + (thumbRange * ((value - min) / (range - extent))));
 
1489
                }
 
1490
 
 
1491
                /*
 
1492
                 * If the buttons don't fit, allocate half of the available space to
 
1493
                 * each and move the lower one (incrButton) down.
 
1494
                 */
 
1495
                int sbAvailButtonH = (sbSize.height - sbInsetsH);
 
1496
                if (sbAvailButtonH < sbButtonsH) {
 
1497
                        incrButtonH = 0;// decrButton2H = 0;
 
1498
                        // incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH);
 
1499
                }
 
1500
                this.decrButton.setBounds(0, 0, 0, 0);
 
1501
                this.mySecondDecreaseButton.setBounds(0, 0, 0, 0);
 
1502
                this.incrButton.setBounds(0, 0, 0, 0);
 
1503
                this.mySecondIncreaseButton.setBounds(0, 0, 0, 0);
 
1504
 
 
1505
                /*
 
1506
                 * Update the trackRect field.
 
1507
                 */
 
1508
                int itrackY = 0;
 
1509
                int itrackH = decrButton2Y - itrackY;
 
1510
                this.trackRect.setBounds(itemX, itrackY, itemW, itrackH);
 
1511
 
 
1512
                /*
 
1513
                 * If the thumb isn't going to fit, zero it's bounds. Otherwise make
 
1514
                 * sure it fits between the buttons. Note that setting the thumbs bounds
 
1515
                 * will cause a repaint.
 
1516
                 */
 
1517
                if (thumbH >= (int) trackH) {
 
1518
                        this.setThumbBounds(0, 0, 0, 0);
 
1519
                } else {
 
1520
                        if ((thumbY + thumbH) > decrButton2Y) {
 
1521
                                thumbY = decrButton2Y - thumbH;
 
1522
                        }
 
1523
                        if (thumbY < 0) {
 
1524
                                thumbY = 0;
 
1525
                        }
 
1526
                        this.setThumbBounds(itemX, thumbY, itemW, thumbH);
 
1527
                }
 
1528
        }
 
1529
 
 
1530
        /**
 
1531
         * Lays out the vertical scroll bar when the button policy is
 
1532
         * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#MULTIPLE}.
 
1533
         * 
 
1534
         * @param sb
 
1535
         *            Scroll bar.
 
1536
         */
 
1537
        protected void layoutVScrollbarMultiple(JScrollBar sb) {
 
1538
                Dimension sbSize = sb.getSize();
 
1539
                Insets sbInsets = sb.getInsets();
 
1540
 
 
1541
                /*
 
1542
                 * Width and left edge of the buttons and thumb.
 
1543
                 */
 
1544
                int itemW = sbSize.width - (sbInsets.left + sbInsets.right);
 
1545
                int itemX = sbInsets.left;
 
1546
 
 
1547
                /*
 
1548
                 * Nominal locations of the buttons, assuming their preferred size will
 
1549
                 * fit.
 
1550
                 */
 
1551
                int incrButtonH = itemW;
 
1552
                int incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH);
 
1553
 
 
1554
                int decrButton2H = itemW;
 
1555
                int decrButton2Y = incrButtonY - decrButton2H;
 
1556
 
 
1557
                int decrButtonH = itemW;
 
1558
                int decrButtonY = sbInsets.top;
 
1559
 
 
1560
                /*
 
1561
                 * The thumb must fit within the height left over after we subtract the
 
1562
                 * preferredSize of the buttons and the insets.
 
1563
                 */
 
1564
                int sbInsetsH = sbInsets.top + sbInsets.bottom;
 
1565
                int sbButtonsH = decrButton2H + incrButtonH + decrButtonH;
 
1566
                float trackH = sbSize.height - (sbInsetsH + sbButtonsH);
 
1567
 
 
1568
                /*
 
1569
                 * Compute the height and origin of the thumb. The case where the thumb
 
1570
                 * is at the bottom edge is handled specially to avoid numerical
 
1571
                 * problems in computing thumbY. Enforce the thumbs min/max dimensions.
 
1572
                 * If the thumb doesn't fit in the track (trackH) we'll hide it later.
 
1573
                 */
 
1574
                float min = sb.getMinimum();
 
1575
                float extent = sb.getVisibleAmount();
 
1576
                float range = sb.getMaximum() - min;
 
1577
                float value = sb.getValue();
 
1578
 
 
1579
                int thumbH = (range <= 0) ? this.getMaximumThumbSize().height
 
1580
                                : (int) (trackH * (extent / range));
 
1581
                thumbH = Math.max(thumbH, this.getMinimumThumbSize().height);
 
1582
                thumbH = Math.min(thumbH, this.getMaximumThumbSize().height);
 
1583
 
 
1584
                int thumbY = decrButton2Y - thumbH;
 
1585
                if (value < (sb.getMaximum() - sb.getVisibleAmount())) {
 
1586
                        float thumbRange = trackH - thumbH;
 
1587
                        thumbY = (int) (0.5f + (thumbRange * ((value - min) / (range - extent))));
 
1588
                        thumbY += decrButtonY + decrButtonH;
 
1589
                }
 
1590
 
 
1591
                /*
 
1592
                 * If the buttons don't fit, allocate half of the available space to
 
1593
                 * each and move the lower one (incrButton) down.
 
1594
                 */
 
1595
                int sbAvailButtonH = (sbSize.height - sbInsetsH);
 
1596
                if (sbAvailButtonH < sbButtonsH) {
 
1597
                        incrButtonH = decrButton2H = decrButtonH = sbAvailButtonH / 2;
 
1598
                        incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH);
 
1599
                }
 
1600
                this.decrButton.setBounds(itemX, decrButtonY, itemW, decrButtonH);
 
1601
                this.mySecondDecreaseButton.setBounds(itemX,
 
1602
                                incrButtonY - decrButton2H, itemW, decrButton2H);
 
1603
                this.incrButton.setBounds(itemX, incrButtonY - 1, itemW,
 
1604
                                incrButtonH + 1);
 
1605
                this.mySecondIncreaseButton.setBounds(0, 0, 0, 0);
 
1606
 
 
1607
                /*
 
1608
                 * Update the trackRect field.
 
1609
                 */
 
1610
                int itrackY = decrButtonY + decrButtonH;
 
1611
                int itrackH = decrButton2Y - itrackY;
 
1612
                this.trackRect.setBounds(itemX, itrackY, itemW, itrackH);
 
1613
 
 
1614
                /*
 
1615
                 * If the thumb isn't going to fit, zero it's bounds. Otherwise make
 
1616
                 * sure it fits between the buttons. Note that setting the thumbs bounds
 
1617
                 * will cause a repaint.
 
1618
                 */
 
1619
                if (thumbH >= (int) trackH) {
 
1620
                        this.setThumbBounds(0, 0, 0, 0);
 
1621
                } else {
 
1622
                        if ((thumbY + thumbH) > decrButton2Y) {
 
1623
                                thumbY = decrButton2Y - thumbH;
 
1624
                        }
 
1625
                        if (thumbY < (decrButtonY + decrButtonH)) {
 
1626
                                thumbY = decrButtonY + decrButtonH + 1;
 
1627
                        }
 
1628
                        this.setThumbBounds(itemX, thumbY, itemW, thumbH);
 
1629
                }
 
1630
        }
 
1631
 
 
1632
        /**
 
1633
         * Lays out the vertical scroll bar when the button policy is
 
1634
         * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#MULTIPLE_BOTH}.
 
1635
         * 
 
1636
         * @param sb
 
1637
         *            Scroll bar.
 
1638
         */
 
1639
        protected void layoutVScrollbarMultipleBoth(JScrollBar sb) {
 
1640
                Dimension sbSize = sb.getSize();
 
1641
                Insets sbInsets = sb.getInsets();
 
1642
 
 
1643
                /*
 
1644
                 * Width and left edge of the buttons and thumb.
 
1645
                 */
 
1646
                int itemW = sbSize.width - (sbInsets.left + sbInsets.right);
 
1647
                int itemX = sbInsets.left;
 
1648
 
 
1649
                /*
 
1650
                 * Nominal locations of the buttons, assuming their preferred size will
 
1651
                 * fit.
 
1652
                 */
 
1653
                int incrButtonH = itemW;
 
1654
                int incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH);
 
1655
 
 
1656
                int decrButton2H = itemW;
 
1657
                int decrButton2Y = incrButtonY - decrButton2H;
 
1658
 
 
1659
                int decrButtonH = itemW;
 
1660
                int decrButtonY = sbInsets.top;
 
1661
 
 
1662
                int incrButton2H = itemW;
 
1663
                int incrButton2Y = decrButtonY + decrButtonH;
 
1664
 
 
1665
                /*
 
1666
                 * The thumb must fit within the height left over after we subtract the
 
1667
                 * preferredSize of the buttons and the insets.
 
1668
                 */
 
1669
                int sbInsetsH = sbInsets.top + sbInsets.bottom;
 
1670
                int sbButtonsH = decrButton2H + incrButtonH + decrButtonH
 
1671
                                + incrButton2H;
 
1672
                float trackH = sbSize.height - (sbInsetsH + sbButtonsH);
 
1673
 
 
1674
                /*
 
1675
                 * Compute the height and origin of the thumb. The case where the thumb
 
1676
                 * is at the bottom edge is handled specially to avoid numerical
 
1677
                 * problems in computing thumbY. Enforce the thumbs min/max dimensions.
 
1678
                 * If the thumb doesn't fit in the track (trackH) we'll hide it later.
 
1679
                 */
 
1680
                float min = sb.getMinimum();
 
1681
                float extent = sb.getVisibleAmount();
 
1682
                float range = sb.getMaximum() - min;
 
1683
                float value = sb.getValue();
 
1684
 
 
1685
                int thumbH = (range <= 0) ? this.getMaximumThumbSize().height
 
1686
                                : (int) (trackH * (extent / range));
 
1687
                thumbH = Math.max(thumbH, this.getMinimumThumbSize().height);
 
1688
                thumbH = Math.min(thumbH, this.getMaximumThumbSize().height);
 
1689
 
 
1690
                int thumbY = decrButton2Y - thumbH;
 
1691
                if (value < (sb.getMaximum() - sb.getVisibleAmount())) {
 
1692
                        float thumbRange = trackH - thumbH;
 
1693
                        thumbY = (int) (0.5f + (thumbRange * ((value - min) / (range - extent))));
 
1694
                        thumbY += incrButton2Y + incrButton2H;
 
1695
                }
 
1696
 
 
1697
                /*
 
1698
                 * If the buttons don't fit, allocate half of the available space to
 
1699
                 * each and move the lower one (incrButton) down.
 
1700
                 */
 
1701
                int sbAvailButtonH = (sbSize.height - sbInsetsH);
 
1702
                if (sbAvailButtonH < sbButtonsH) {
 
1703
                        incrButtonH = decrButton2H = decrButtonH = incrButton2H = sbAvailButtonH / 4;
 
1704
                        incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH);
 
1705
                }
 
1706
                this.decrButton.setBounds(itemX, decrButtonY, itemW, decrButtonH);
 
1707
                this.mySecondDecreaseButton.setBounds(itemX,
 
1708
                                incrButtonY - decrButton2H, itemW, decrButton2H);
 
1709
                this.incrButton.setBounds(itemX, incrButtonY - 1, itemW,
 
1710
                                incrButtonH + 1);
 
1711
                this.mySecondIncreaseButton.setBounds(itemX, decrButtonY + decrButtonH
 
1712
                                - 1, itemW, incrButton2H + 1);
 
1713
 
 
1714
                /*
 
1715
                 * Update the trackRect field.
 
1716
                 */
 
1717
                int itrackY = incrButton2Y + incrButton2H;
 
1718
                int itrackH = decrButton2Y - itrackY;
 
1719
                this.trackRect.setBounds(itemX, itrackY, itemW, itrackH);
 
1720
 
 
1721
                /*
 
1722
                 * If the thumb isn't going to fit, zero it's bounds. Otherwise make
 
1723
                 * sure it fits between the buttons. Note that setting the thumbs bounds
 
1724
                 * will cause a repaint.
 
1725
                 */
 
1726
                if (thumbH >= (int) trackH) {
 
1727
                        this.setThumbBounds(0, 0, 0, 0);
 
1728
                } else {
 
1729
                        if ((thumbY + thumbH) > decrButton2Y) {
 
1730
                                thumbY = decrButton2Y - thumbH;
 
1731
                        }
 
1732
                        if (thumbY < (incrButton2Y + incrButton2H)) {
 
1733
                                thumbY = incrButton2Y + incrButton2H + 1;
 
1734
                        }
 
1735
                        this.setThumbBounds(itemX, thumbY, itemW, thumbH);
 
1736
                }
 
1737
        }
 
1738
 
 
1739
        /**
 
1740
         * Lays out the horizontal scroll bar when the button policy is
 
1741
         * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#ADJACENT}.
 
1742
         * 
 
1743
         * @param sb
 
1744
         *            Scroll bar.
 
1745
         */
 
1746
        protected void layoutHScrollbarAdjacent(JScrollBar sb) {
 
1747
                Dimension sbSize = sb.getSize();
 
1748
                Insets sbInsets = sb.getInsets();
 
1749
 
 
1750
                /*
 
1751
                 * Height and top edge of the buttons and thumb.
 
1752
                 */
 
1753
                int itemH = sbSize.height - (sbInsets.top + sbInsets.bottom);
 
1754
                int itemY = sbInsets.top;
 
1755
 
 
1756
                boolean ltr = sb.getComponentOrientation().isLeftToRight();
 
1757
 
 
1758
                /*
 
1759
                 * Nominal locations of the buttons, assuming their preferred size will
 
1760
                 * fit.
 
1761
                 */
 
1762
                int decrButton2W = itemH;
 
1763
                int incrButtonW = itemH;
 
1764
                int incrButtonX = ltr ? sbSize.width - (sbInsets.right + incrButtonW)
 
1765
                                : sbInsets.left;
 
1766
                int decrButton2X = ltr ? incrButtonX - decrButton2W : incrButtonX
 
1767
                                + decrButton2W;
 
1768
 
 
1769
                /*
 
1770
                 * The thumb must fit within the width left over after we subtract the
 
1771
                 * preferredSize of the buttons and the insets.
 
1772
                 */
 
1773
                int sbInsetsW = sbInsets.left + sbInsets.right;
 
1774
                int sbButtonsW = decrButton2W + incrButtonW;
 
1775
                float trackW = sbSize.width - (sbInsetsW + sbButtonsW);
 
1776
 
 
1777
                /*
 
1778
                 * Compute the width and origin of the thumb. Enforce the thumbs min/max
 
1779
                 * dimensions. The case where the thumb is at the right edge is handled
 
1780
                 * specially to avoid numerical problems in computing thumbX. If the
 
1781
                 * thumb doesn't fit in the track (trackH) we'll hide it later.
 
1782
                 */
 
1783
                float min = sb.getMinimum();
 
1784
                float max = sb.getMaximum();
 
1785
                float extent = sb.getVisibleAmount();
 
1786
                float range = max - min;
 
1787
                float value = sb.getValue();
 
1788
 
 
1789
                int thumbW = (range <= 0) ? this.getMaximumThumbSize().width
 
1790
                                : (int) (trackW * (extent / range));
 
1791
                thumbW = Math.max(thumbW, this.getMinimumThumbSize().width);
 
1792
                thumbW = Math.min(thumbW, this.getMaximumThumbSize().width);
 
1793
 
 
1794
                int thumbX = ltr ? decrButton2X - thumbW : sbInsets.left;
 
1795
                if (value < (max - sb.getVisibleAmount())) {
 
1796
                        float thumbRange = trackW - thumbW;
 
1797
                        if (ltr) {
 
1798
                                thumbX = (int) (0.5f + (thumbRange * ((value - min) / (range - extent))));
 
1799
                        } else {
 
1800
                                thumbX = (int) (0.5f + (thumbRange * ((max - extent - value) / (range - extent))));
 
1801
                                thumbX += decrButton2X + decrButton2W;
 
1802
                        }
 
1803
                }
 
1804
 
 
1805
                /*
 
1806
                 * If the buttons don't fit, allocate half of the available space to
 
1807
                 * each and move the right one over.
 
1808
                 */
 
1809
                int sbAvailButtonW = (sbSize.width - sbInsetsW);
 
1810
                if (sbAvailButtonW < sbButtonsW) {
 
1811
                        incrButtonW = decrButton2W = sbAvailButtonW / 2;
 
1812
                        incrButtonX = ltr ? sbSize.width - (sbInsets.right + incrButtonW)
 
1813
                                        : sbInsets.left;
 
1814
                }
 
1815
 
 
1816
                this.mySecondDecreaseButton.setBounds(decrButton2X + (ltr ? 0 : -1),
 
1817
                                itemY, decrButton2W + 1, itemH);
 
1818
                this.incrButton.setBounds(incrButtonX, itemY, incrButtonW, itemH);
 
1819
                this.decrButton.setBounds(0, 0, 0, 0);
 
1820
                this.mySecondIncreaseButton.setBounds(0, 0, 0, 0);
 
1821
 
 
1822
                /*
 
1823
                 * Update the trackRect field.
 
1824
                 */
 
1825
                if (ltr) {
 
1826
                        int itrackX = sbInsets.left;
 
1827
                        int itrackW = decrButton2X - itrackX;
 
1828
                        this.trackRect.setBounds(itrackX, itemY, itrackW, itemH);
 
1829
                } else {
 
1830
                        int itrackX = decrButton2X + decrButton2W;
 
1831
                        int itrackW = sbSize.width - itrackX;
 
1832
                        this.trackRect.setBounds(itrackX, itemY, itrackW, itemH);
 
1833
                }
 
1834
 
 
1835
                /*
 
1836
                 * Make sure the thumb fits between the buttons. Note that setting the
 
1837
                 * thumbs bounds causes a repaint.
 
1838
                 */
 
1839
                if (thumbW >= (int) trackW) {
 
1840
                        this.setThumbBounds(0, 0, 0, 0);
 
1841
                } else {
 
1842
                        if (ltr) {
 
1843
                                if (thumbX + thumbW > decrButton2X) {
 
1844
                                        thumbX = decrButton2X - thumbW;
 
1845
                                }
 
1846
                                if (thumbX < 0) {
 
1847
                                        thumbX = 1;
 
1848
                                }
 
1849
                        } else {
 
1850
                                if (thumbX + thumbW > (sbSize.width - sbInsets.left)) {
 
1851
                                        thumbX = sbSize.width - sbInsets.left - thumbW;
 
1852
                                }
 
1853
                                if (thumbX < (decrButton2X + decrButton2W)) {
 
1854
                                        thumbX = decrButton2X + decrButton2W + 1;
 
1855
                                }
 
1856
                        }
 
1857
                        this.setThumbBounds(thumbX, itemY, thumbW, itemH);
 
1858
                }
 
1859
        }
 
1860
 
 
1861
        /**
 
1862
         * Lays out the horizontal scroll bar when the button policy is
 
1863
         * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#NONE}.
 
1864
         * 
 
1865
         * @param sb
 
1866
         *            Scroll bar.
 
1867
         */
 
1868
        protected void layoutHScrollbarNone(JScrollBar sb) {
 
1869
                Dimension sbSize = sb.getSize();
 
1870
                Insets sbInsets = sb.getInsets();
 
1871
 
 
1872
                /*
 
1873
                 * Height and top edge of the buttons and thumb.
 
1874
                 */
 
1875
                int itemH = sbSize.height - (sbInsets.top + sbInsets.bottom);
 
1876
                int itemY = sbInsets.top;
 
1877
 
 
1878
                boolean ltr = sb.getComponentOrientation().isLeftToRight();
 
1879
 
 
1880
                /*
 
1881
                 * Nominal locations of the buttons, assuming their preferred size will
 
1882
                 * fit.
 
1883
                 */
 
1884
                int decrButton2W = 0;
 
1885
                int incrButtonW = 0;
 
1886
                int incrButtonX = ltr ? sbSize.width - (sbInsets.right + incrButtonW)
 
1887
                                : sbInsets.left;
 
1888
                int decrButton2X = ltr ? incrButtonX - decrButton2W : incrButtonX
 
1889
                                + decrButton2W;
 
1890
 
 
1891
                /*
 
1892
                 * The thumb must fit within the width left over after we subtract the
 
1893
                 * preferredSize of the buttons and the insets.
 
1894
                 */
 
1895
                int sbInsetsW = sbInsets.left + sbInsets.right;
 
1896
                int sbButtonsW = decrButton2W + incrButtonW;
 
1897
                float trackW = sbSize.width - (sbInsetsW + sbButtonsW);
 
1898
 
 
1899
                /*
 
1900
                 * Compute the width and origin of the thumb. Enforce the thumbs min/max
 
1901
                 * dimensions. The case where the thumb is at the right edge is handled
 
1902
                 * specially to avoid numerical problems in computing thumbX. If the
 
1903
                 * thumb doesn't fit in the track (trackH) we'll hide it later.
 
1904
                 */
 
1905
                float min = sb.getMinimum();
 
1906
                float max = sb.getMaximum();
 
1907
                float extent = sb.getVisibleAmount();
 
1908
                float range = max - min;
 
1909
                float value = sb.getValue();
 
1910
 
 
1911
                int thumbW = (range <= 0) ? this.getMaximumThumbSize().width
 
1912
                                : (int) (trackW * (extent / range));
 
1913
                thumbW = Math.max(thumbW, this.getMinimumThumbSize().width);
 
1914
                thumbW = Math.min(thumbW, this.getMaximumThumbSize().width);
 
1915
 
 
1916
                int thumbX = ltr ? decrButton2X - thumbW : sbInsets.left;
 
1917
                if (value < (max - sb.getVisibleAmount())) {
 
1918
                        float thumbRange = trackW - thumbW;
 
1919
                        if (ltr) {
 
1920
                                thumbX = (int) (0.5f + (thumbRange * ((value - min) / (range - extent))));
 
1921
                        } else {
 
1922
                                thumbX = (int) (0.5f + (thumbRange * ((max - extent - value) / (range - extent))));
 
1923
                                thumbX += decrButton2X + decrButton2W;
 
1924
                        }
 
1925
                }
 
1926
 
 
1927
                /*
 
1928
                 * If the buttons don't fit, allocate half of the available space to
 
1929
                 * each and move the right one over.
 
1930
                 */
 
1931
                int sbAvailButtonW = (sbSize.width - sbInsetsW);
 
1932
                if (sbAvailButtonW < sbButtonsW) {
 
1933
                        incrButtonW = decrButton2W = 0;
 
1934
                        // incrButtonX = ltr ? sbSize.width - (sbInsets.right + incrButtonW)
 
1935
                        // : sbInsets.left;
 
1936
                }
 
1937
 
 
1938
                this.incrButton.setBounds(0, 0, 0, 0);
 
1939
                this.decrButton.setBounds(0, 0, 0, 0);
 
1940
                this.mySecondIncreaseButton.setBounds(0, 0, 0, 0);
 
1941
                this.mySecondDecreaseButton.setBounds(0, 0, 0, 0);
 
1942
 
 
1943
                /*
 
1944
                 * Update the trackRect field.
 
1945
                 */
 
1946
                if (ltr) {
 
1947
                        int itrackX = sbInsets.left;
 
1948
                        int itrackW = decrButton2X - itrackX;
 
1949
                        this.trackRect.setBounds(itrackX, itemY, itrackW, itemH);
 
1950
                } else {
 
1951
                        int itrackX = decrButton2X + decrButton2W;
 
1952
                        int itrackW = sbSize.width - itrackX;
 
1953
                        this.trackRect.setBounds(itrackX, itemY, itrackW, itemH);
 
1954
                }
 
1955
 
 
1956
                /*
 
1957
                 * Make sure the thumb fits between the buttons. Note that setting the
 
1958
                 * thumbs bounds causes a repaint.
 
1959
                 */
 
1960
                if (thumbW >= (int) trackW) {
 
1961
                        this.setThumbBounds(0, 0, 0, 0);
 
1962
                } else {
 
1963
                        if (ltr) {
 
1964
                                if (thumbX + thumbW > decrButton2X) {
 
1965
                                        thumbX = decrButton2X - thumbW;
 
1966
                                }
 
1967
                                if (thumbX < 0) {
 
1968
                                        thumbX = 1;
 
1969
                                }
 
1970
                        } else {
 
1971
                                if (thumbX + thumbW > (sbSize.width - sbInsets.left)) {
 
1972
                                        thumbX = sbSize.width - sbInsets.left - thumbW;
 
1973
                                }
 
1974
                                if (thumbX < (decrButton2X + decrButton2W)) {
 
1975
                                        thumbX = decrButton2X + decrButton2W + 1;
 
1976
                                }
 
1977
                        }
 
1978
                        this.setThumbBounds(thumbX, itemY, thumbW, itemH);
 
1979
                }
 
1980
        }
 
1981
 
 
1982
        /**
 
1983
         * Lays out the horizontal scroll bar when the button policy is
 
1984
         * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#MULTIPLE}.
 
1985
         * 
 
1986
         * @param sb
 
1987
         *            Scroll bar.
 
1988
         */
 
1989
        protected void layoutHScrollbarMultiple(JScrollBar sb) {
 
1990
                Dimension sbSize = sb.getSize();
 
1991
                Insets sbInsets = sb.getInsets();
 
1992
 
 
1993
                /*
 
1994
                 * Height and top edge of the buttons and thumb.
 
1995
                 */
 
1996
                int itemH = sbSize.height - (sbInsets.top + sbInsets.bottom);
 
1997
                int itemY = sbInsets.top;
 
1998
 
 
1999
                boolean ltr = sb.getComponentOrientation().isLeftToRight();
 
2000
 
 
2001
                /*
 
2002
                 * Nominal locations of the buttons, assuming their preferred size will
 
2003
                 * fit.
 
2004
                 */
 
2005
                int decrButton2W = itemH;
 
2006
                int decrButtonW = itemH;
 
2007
                int incrButtonW = itemH;
 
2008
                int incrButtonX = ltr ? sbSize.width - (sbInsets.right + incrButtonW)
 
2009
                                : sbInsets.left;
 
2010
                int decrButton2X = ltr ? incrButtonX - decrButton2W : incrButtonX
 
2011
                                + decrButton2W;
 
2012
                int decrButtonX = ltr ? sbInsets.left : sbSize.width - sbInsets.right
 
2013
                                - decrButtonW;
 
2014
 
 
2015
                /*
 
2016
                 * The thumb must fit within the width left over after we subtract the
 
2017
                 * preferredSize of the buttons and the insets.
 
2018
                 */
 
2019
                int sbInsetsW = sbInsets.left + sbInsets.right;
 
2020
                int sbButtonsW = decrButton2W + incrButtonW + decrButtonW;
 
2021
                float trackW = sbSize.width - (sbInsetsW + sbButtonsW);
 
2022
 
 
2023
                /*
 
2024
                 * Compute the width and origin of the thumb. Enforce the thumbs min/max
 
2025
                 * dimensions. The case where the thumb is at the right edge is handled
 
2026
                 * specially to avoid numerical problems in computing thumbX. If the
 
2027
                 * thumb doesn't fit in the track (trackH) we'll hide it later.
 
2028
                 */
 
2029
                float min = sb.getMinimum();
 
2030
                float max = sb.getMaximum();
 
2031
                float extent = sb.getVisibleAmount();
 
2032
                float range = max - min;
 
2033
                float value = sb.getValue();
 
2034
 
 
2035
                int thumbW = (range <= 0) ? this.getMaximumThumbSize().width
 
2036
                                : (int) (trackW * (extent / range));
 
2037
                thumbW = Math.max(thumbW, this.getMinimumThumbSize().width);
 
2038
                thumbW = Math.min(thumbW, this.getMaximumThumbSize().width);
 
2039
 
 
2040
                int thumbX = ltr ? decrButton2X - thumbW : sbInsets.left;
 
2041
                if (value < (max - sb.getVisibleAmount())) {
 
2042
                        float thumbRange = trackW - thumbW;
 
2043
                        if (ltr) {
 
2044
                                thumbX = (int) (0.5f + (thumbRange * ((value - min) / (range - extent))));
 
2045
                                thumbX += decrButtonX + decrButtonW;
 
2046
                        } else {
 
2047
                                thumbX = (int) (0.5f + (thumbRange * ((max - extent - value) / (range - extent))));
 
2048
                                thumbX += decrButton2X + decrButton2W;
 
2049
                        }
 
2050
                }
 
2051
 
 
2052
                /*
 
2053
                 * If the buttons don't fit, allocate half of the available space to
 
2054
                 * each and move the right one over.
 
2055
                 */
 
2056
                int sbAvailButtonW = (sbSize.width - sbInsetsW);
 
2057
                if (sbAvailButtonW < sbButtonsW) {
 
2058
                        incrButtonW = decrButton2W = decrButtonW = sbAvailButtonW / 2;
 
2059
                        incrButtonX = ltr ? sbSize.width - (sbInsets.right + incrButtonW)
 
2060
                                        : sbInsets.left;
 
2061
                }
 
2062
 
 
2063
                this.mySecondDecreaseButton.setBounds(decrButton2X + (ltr ? 0 : -1),
 
2064
                                itemY, decrButton2W + 1, itemH);
 
2065
                this.incrButton.setBounds(incrButtonX, itemY, incrButtonW, itemH);
 
2066
                this.decrButton.setBounds(decrButtonX, itemY, decrButtonW, itemH);
 
2067
                this.mySecondIncreaseButton.setBounds(0, 0, 0, 0);
 
2068
 
 
2069
                /*
 
2070
                 * Update the trackRect field.
 
2071
                 */
 
2072
                if (ltr) {
 
2073
                        int itrackX = decrButtonX + decrButtonW;
 
2074
                        int itrackW = decrButton2X - itrackX;
 
2075
                        this.trackRect.setBounds(itrackX, itemY, itrackW, itemH);
 
2076
                } else {
 
2077
                        int itrackX = decrButton2X + decrButton2W;
 
2078
                        int itrackW = decrButtonX - itrackX;
 
2079
                        this.trackRect.setBounds(itrackX, itemY, itrackW, itemH);
 
2080
                }
 
2081
 
 
2082
                /*
 
2083
                 * Make sure the thumb fits between the buttons. Note that setting the
 
2084
                 * thumbs bounds causes a repaint.
 
2085
                 */
 
2086
                if (thumbW >= (int) trackW) {
 
2087
                        this.setThumbBounds(0, 0, 0, 0);
 
2088
                } else {
 
2089
                        if (ltr) {
 
2090
                                if (thumbX + thumbW > decrButton2X) {
 
2091
                                        thumbX = decrButton2X - thumbW;
 
2092
                                }
 
2093
                                if (thumbX < (decrButtonX + decrButtonW)) {
 
2094
                                        thumbX = decrButtonX + decrButtonW + 1;
 
2095
                                }
 
2096
                        } else {
 
2097
                                if (thumbX + thumbW > decrButtonX) {
 
2098
                                        thumbX = decrButtonX - thumbW;
 
2099
                                }
 
2100
                                if (thumbX < (decrButton2X + decrButton2W)) {
 
2101
                                        thumbX = decrButton2X + decrButton2W + 1;
 
2102
                                }
 
2103
                        }
 
2104
                        this.setThumbBounds(thumbX, itemY, thumbW, itemH);
 
2105
                }
 
2106
        }
 
2107
 
 
2108
        /**
 
2109
         * Lays out the horizontal scroll bar when the button policy is
 
2110
         * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#MULTIPLE}.
 
2111
         * 
 
2112
         * @param sb
 
2113
         *            Scroll bar.
 
2114
         */
 
2115
        protected void layoutHScrollbarMultipleBoth(JScrollBar sb) {
 
2116
                Dimension sbSize = sb.getSize();
 
2117
                Insets sbInsets = sb.getInsets();
 
2118
 
 
2119
                /*
 
2120
                 * Height and top edge of the buttons and thumb.
 
2121
                 */
 
2122
                int itemH = sbSize.height - (sbInsets.top + sbInsets.bottom);
 
2123
                int itemY = sbInsets.top;
 
2124
 
 
2125
                boolean ltr = sb.getComponentOrientation().isLeftToRight();
 
2126
 
 
2127
                /*
 
2128
                 * Nominal locations of the buttons, assuming their preferred size will
 
2129
                 * fit.
 
2130
                 */
 
2131
                int decrButton2W = itemH;
 
2132
                int incrButton2W = itemH;
 
2133
                int decrButtonW = itemH;
 
2134
                int incrButtonW = itemH;
 
2135
 
 
2136
                int incrButtonX = ltr ? sbSize.width - (sbInsets.right + incrButtonW)
 
2137
                                : sbInsets.left;
 
2138
                int decrButton2X = ltr ? incrButtonX - decrButton2W : incrButtonX
 
2139
                                + decrButton2W;
 
2140
                int decrButtonX = ltr ? sbInsets.left : sbSize.width - sbInsets.right
 
2141
                                - decrButtonW;
 
2142
                int incrButton2X = ltr ? decrButtonX + decrButtonW : decrButtonX
 
2143
                                - incrButton2W;
 
2144
 
 
2145
                /*
 
2146
                 * The thumb must fit within the width left over after we subtract the
 
2147
                 * preferredSize of the buttons and the insets.
 
2148
                 */
 
2149
                int sbInsetsW = sbInsets.left + sbInsets.right;
 
2150
                int sbButtonsW = decrButton2W + incrButtonW + decrButtonW
 
2151
                                + incrButton2W;
 
2152
                float trackW = sbSize.width - (sbInsetsW + sbButtonsW);
 
2153
 
 
2154
                /*
 
2155
                 * Compute the width and origin of the thumb. Enforce the thumbs min/max
 
2156
                 * dimensions. The case where the thumb is at the right edge is handled
 
2157
                 * specially to avoid numerical problems in computing thumbX. If the
 
2158
                 * thumb doesn't fit in the track (trackH) we'll hide it later.
 
2159
                 */
 
2160
                float min = sb.getMinimum();
 
2161
                float max = sb.getMaximum();
 
2162
                float extent = sb.getVisibleAmount();
 
2163
                float range = max - min;
 
2164
                float value = sb.getValue();
 
2165
 
 
2166
                int thumbW = (range <= 0) ? this.getMaximumThumbSize().width
 
2167
                                : (int) (trackW * (extent / range));
 
2168
                thumbW = Math.max(thumbW, this.getMinimumThumbSize().width);
 
2169
                thumbW = Math.min(thumbW, this.getMaximumThumbSize().width);
 
2170
 
 
2171
                int thumbX = ltr ? decrButton2X - thumbW : sbInsets.left;
 
2172
                if (value < (max - sb.getVisibleAmount())) {
 
2173
                        float thumbRange = trackW - thumbW;
 
2174
                        if (ltr) {
 
2175
                                thumbX = (int) (0.5f + (thumbRange * ((value - min) / (range - extent))));
 
2176
                                thumbX += incrButton2X + incrButton2W;
 
2177
                        } else {
 
2178
                                thumbX = (int) (0.5f + (thumbRange * ((max - extent - value) / (range - extent))));
 
2179
                                thumbX += decrButton2X + decrButton2W;
 
2180
                        }
 
2181
                }
 
2182
 
 
2183
                /*
 
2184
                 * If the buttons don't fit, allocate half of the available space to
 
2185
                 * each and move the right one over.
 
2186
                 */
 
2187
                int sbAvailButtonW = (sbSize.width - sbInsetsW);
 
2188
                if (sbAvailButtonW < sbButtonsW) {
 
2189
                        incrButtonW = decrButton2W = decrButtonW = incrButton2W = sbAvailButtonW / 4;
 
2190
                        incrButtonX = ltr ? sbSize.width - (sbInsets.right + incrButtonW)
 
2191
                                        : sbInsets.left;
 
2192
                }
 
2193
 
 
2194
                this.mySecondDecreaseButton.setBounds(decrButton2X + (ltr ? 0 : -1),
 
2195
                                itemY, decrButton2W + 1, itemH);
 
2196
                this.mySecondIncreaseButton.setBounds(incrButton2X + (ltr ? -1 : 0),
 
2197
                                itemY, incrButton2W + 1, itemH);
 
2198
                this.incrButton.setBounds(incrButtonX, itemY, incrButtonW, itemH);
 
2199
                this.decrButton.setBounds(decrButtonX, itemY, decrButtonW, itemH);
 
2200
 
 
2201
                /*
 
2202
                 * Update the trackRect field.
 
2203
                 */
 
2204
                if (ltr) {
 
2205
                        int itrackX = incrButton2X + incrButton2W;
 
2206
                        int itrackW = decrButton2X - itrackX;
 
2207
                        this.trackRect.setBounds(itrackX, itemY, itrackW, itemH);
 
2208
                } else {
 
2209
                        int itrackX = decrButton2X + decrButton2W;
 
2210
                        int itrackW = incrButton2X - itrackX;
 
2211
                        this.trackRect.setBounds(itrackX, itemY, itrackW, itemH);
 
2212
                }
 
2213
 
 
2214
                /*
 
2215
                 * Make sure the thumb fits between the buttons. Note that setting the
 
2216
                 * thumbs bounds causes a repaint.
 
2217
                 */
 
2218
                if (thumbW >= (int) trackW) {
 
2219
                        this.setThumbBounds(0, 0, 0, 0);
 
2220
                } else {
 
2221
                        if (ltr) {
 
2222
                                if (thumbX + thumbW > decrButton2X) {
 
2223
                                        thumbX = decrButton2X - thumbW;
 
2224
                                }
 
2225
                                if (thumbX < (incrButton2X + incrButton2W)) {
 
2226
                                        thumbX = incrButton2X + incrButton2W + 1;
 
2227
                                }
 
2228
                        } else {
 
2229
                                if (thumbX + thumbW > incrButton2X) {
 
2230
                                        thumbX = incrButton2X - thumbW;
 
2231
                                }
 
2232
                                if (thumbX < (decrButton2X + decrButton2W)) {
 
2233
                                        thumbX = decrButton2X + decrButton2W + 1;
 
2234
                                }
 
2235
                        }
 
2236
                        this.setThumbBounds(thumbX, itemY, thumbW, itemH);
 
2237
                }
 
2238
        }
 
2239
 
 
2240
        /**
 
2241
         * Returns the memory usage string.
 
2242
         * 
 
2243
         * @return The memory usage string.
 
2244
         */
 
2245
        public static String getMemoryUsage() {
 
2246
                StringBuffer sb = new StringBuffer();
 
2247
                sb.append("SubstanceScrollBarUI: \n");
 
2248
                sb.append("\t" + thumbHorizontalMap.size() + " thumb horizontal, "
 
2249
                                + thumbVerticalMap.size() + " thumb vertical");
 
2250
                sb.append("\t" + trackHorizontalMap.size() + " track horizontal, "
 
2251
                                + trackVerticalMap.size() + " track vertical");
 
2252
                return sb.toString();
 
2253
        }
 
2254
 
 
2255
        /*
 
2256
         * (non-Javadoc)
 
2257
         * 
 
2258
         * @see javax.swing.plaf.basic.BasicScrollBarUI#createTrackListener()
 
2259
         */
 
2260
        @Override
 
2261
        protected TrackListener createTrackListener() {
 
2262
                return new SubstanceTrackListener();
 
2263
        }
 
2264
 
 
2265
        /**
 
2266
         * Track mouse drags. Had to take this one from BasicScrollBarUI since the
 
2267
         * setValueForm method is private.
 
2268
         */
 
2269
        protected class SubstanceTrackListener extends TrackListener {
 
2270
                /**
 
2271
                 * Current scroll direction.
 
2272
                 */
 
2273
                private transient int direction = +1;
 
2274
 
 
2275
                /*
 
2276
                 * (non-Javadoc)
 
2277
                 * 
 
2278
                 * @see
 
2279
                 * javax.swing.plaf.basic.BasicScrollBarUI$TrackListener#mouseReleased
 
2280
                 * (java.awt.event.MouseEvent)
 
2281
                 */
 
2282
                @Override
 
2283
                public void mouseReleased(MouseEvent e) {
 
2284
                        if (SubstanceScrollBarUI.this.isDragging) {
 
2285
                                SubstanceScrollBarUI.this.updateThumbState(e.getX(), e.getY());
 
2286
                        }
 
2287
                        if (SwingUtilities.isRightMouseButton(e)
 
2288
                                        || (!SubstanceScrollBarUI.this
 
2289
                                                        .getSupportsAbsolutePositioning() && SwingUtilities
 
2290
                                                        .isMiddleMouseButton(e)))
 
2291
                                return;
 
2292
                        if (!SubstanceScrollBarUI.this.scrollbar.isEnabled())
 
2293
                                return;
 
2294
 
 
2295
                        Rectangle r = SubstanceScrollBarUI.this.getTrackBounds();
 
2296
                        SubstanceScrollBarUI.this.scrollbar.repaint(r.x, r.y, r.width,
 
2297
                                        r.height);
 
2298
 
 
2299
                        SubstanceScrollBarUI.this.trackHighlight = NO_HIGHLIGHT;
 
2300
                        SubstanceScrollBarUI.this.isDragging = false;
 
2301
                        this.offset = 0;
 
2302
                        SubstanceScrollBarUI.this.scrollTimer.stop();
 
2303
                        SubstanceScrollBarUI.this.scrollbar.setValueIsAdjusting(false);
 
2304
                }
 
2305
 
 
2306
                /*
 
2307
                 * (non-Javadoc)
 
2308
                 * 
 
2309
                 * @see
 
2310
                 * javax.swing.plaf.basic.BasicScrollBarUI$TrackListener#mousePressed
 
2311
                 * (java.awt.event.MouseEvent)
 
2312
                 */
 
2313
                @Override
 
2314
                public void mousePressed(MouseEvent e) {
 
2315
                        // If the mouse is pressed above the "thumb" component then reduce
 
2316
                        // the scrollbars value by one page ("page up"), otherwise increase
 
2317
                        // it by one page. If there is no thumb then page up if the mouse is
 
2318
                        // in the upper half of the track.
 
2319
                        if (SwingUtilities.isRightMouseButton(e)
 
2320
                                        || (!SubstanceScrollBarUI.this
 
2321
                                                        .getSupportsAbsolutePositioning() && SwingUtilities
 
2322
                                                        .isMiddleMouseButton(e)))
 
2323
                                return;
 
2324
                        if (!SubstanceScrollBarUI.this.scrollbar.isEnabled())
 
2325
                                return;
 
2326
 
 
2327
                        if (!SubstanceScrollBarUI.this.scrollbar.hasFocus()
 
2328
                                        && SubstanceScrollBarUI.this.scrollbar
 
2329
                                                        .isRequestFocusEnabled()) {
 
2330
                                SubstanceScrollBarUI.this.scrollbar.requestFocus();
 
2331
                        }
 
2332
 
 
2333
                        SubstanceScrollBarUI.this.scrollbar.setValueIsAdjusting(true);
 
2334
 
 
2335
                        this.currentMouseX = e.getX();
 
2336
                        this.currentMouseY = e.getY();
 
2337
 
 
2338
                        // Clicked in the Thumb area?
 
2339
                        if (SubstanceScrollBarUI.this.getThumbBounds().contains(
 
2340
                                        this.currentMouseX, this.currentMouseY)) {
 
2341
                                switch (SubstanceScrollBarUI.this.scrollbar.getOrientation()) {
 
2342
                                case JScrollBar.VERTICAL:
 
2343
                                        this.offset = this.currentMouseY
 
2344
                                                        - SubstanceScrollBarUI.this.getThumbBounds().y;
 
2345
                                        break;
 
2346
                                case JScrollBar.HORIZONTAL:
 
2347
                                        this.offset = this.currentMouseX
 
2348
                                                        - SubstanceScrollBarUI.this.getThumbBounds().x;
 
2349
                                        break;
 
2350
                                }
 
2351
                                SubstanceScrollBarUI.this.isDragging = true;
 
2352
                                return;
 
2353
                        } else if (SubstanceScrollBarUI.this
 
2354
                                        .getSupportsAbsolutePositioning()
 
2355
                                        && SwingUtilities.isMiddleMouseButton(e)) {
 
2356
                                switch (SubstanceScrollBarUI.this.scrollbar.getOrientation()) {
 
2357
                                case JScrollBar.VERTICAL:
 
2358
                                        this.offset = SubstanceScrollBarUI.this.getThumbBounds().height / 2;
 
2359
                                        break;
 
2360
                                case JScrollBar.HORIZONTAL:
 
2361
                                        this.offset = SubstanceScrollBarUI.this.getThumbBounds().width / 2;
 
2362
                                        break;
 
2363
                                }
 
2364
                                SubstanceScrollBarUI.this.isDragging = true;
 
2365
                                this.setValueFrom(e);
 
2366
                                return;
 
2367
                        }
 
2368
                        SubstanceScrollBarUI.this.isDragging = false;
 
2369
 
 
2370
                        Dimension sbSize = SubstanceScrollBarUI.this.scrollbar.getSize();
 
2371
                        this.direction = +1;
 
2372
 
 
2373
                        switch (SubstanceScrollBarUI.this.scrollbar.getOrientation()) {
 
2374
                        case JScrollBar.VERTICAL:
 
2375
                                if (SubstanceScrollBarUI.this.getThumbBounds().isEmpty()) {
 
2376
                                        int scrollbarCenter = sbSize.height / 2;
 
2377
                                        this.direction = (this.currentMouseY < scrollbarCenter) ? -1
 
2378
                                                        : +1;
 
2379
                                } else {
 
2380
                                        int thumbY = SubstanceScrollBarUI.this.getThumbBounds().y;
 
2381
                                        this.direction = (this.currentMouseY < thumbY) ? -1 : +1;
 
2382
                                }
 
2383
                                break;
 
2384
                        case JScrollBar.HORIZONTAL:
 
2385
                                if (SubstanceScrollBarUI.this.getThumbBounds().isEmpty()) {
 
2386
                                        int scrollbarCenter = sbSize.width / 2;
 
2387
                                        this.direction = (this.currentMouseX < scrollbarCenter) ? -1
 
2388
                                                        : +1;
 
2389
                                } else {
 
2390
                                        int thumbX = SubstanceScrollBarUI.this.getThumbBounds().x;
 
2391
                                        this.direction = (this.currentMouseX < thumbX) ? -1 : +1;
 
2392
                                }
 
2393
                                if (!SubstanceScrollBarUI.this.scrollbar
 
2394
                                                .getComponentOrientation().isLeftToRight()) {
 
2395
                                        this.direction = -this.direction;
 
2396
                                }
 
2397
                                break;
 
2398
                        }
 
2399
                        SubstanceScrollBarUI.this.scrollByBlock(this.direction);
 
2400
 
 
2401
                        SubstanceScrollBarUI.this.scrollTimer.stop();
 
2402
                        SubstanceScrollBarUI.this.scrollListener
 
2403
                                        .setDirection(this.direction);
 
2404
                        SubstanceScrollBarUI.this.scrollListener.setScrollByBlock(true);
 
2405
                        this.startScrollTimerIfNecessary();
 
2406
                }
 
2407
 
 
2408
                /*
 
2409
                 * (non-Javadoc)
 
2410
                 * 
 
2411
                 * @see
 
2412
                 * javax.swing.plaf.basic.BasicScrollBarUI$TrackListener#mouseDragged
 
2413
                 * (java.awt.event.MouseEvent)
 
2414
                 */
 
2415
                @Override
 
2416
                public void mouseDragged(MouseEvent e) {
 
2417
                        // Set the models value to the position of the thumb's top of
 
2418
                        // Vertical scrollbar, or the left/right of Horizontal scrollbar in
 
2419
                        // LTR / RTL scrollbar relative to the origin of
 
2420
                        // the track.
 
2421
                        if (SwingUtilities.isRightMouseButton(e)
 
2422
                                        || (!SubstanceScrollBarUI.this
 
2423
                                                        .getSupportsAbsolutePositioning() && SwingUtilities
 
2424
                                                        .isMiddleMouseButton(e)))
 
2425
                                return;
 
2426
                        if (!SubstanceScrollBarUI.this.scrollbar.isEnabled()
 
2427
                                        || SubstanceScrollBarUI.this.getThumbBounds().isEmpty()) {
 
2428
                                return;
 
2429
                        }
 
2430
                        if (SubstanceScrollBarUI.this.isDragging) {
 
2431
                                this.setValueFrom(e);
 
2432
                        } else {
 
2433
                                this.currentMouseX = e.getX();
 
2434
                                this.currentMouseY = e.getY();
 
2435
                                SubstanceScrollBarUI.this.updateThumbState(this.currentMouseX,
 
2436
                                                this.currentMouseY);
 
2437
                                this.startScrollTimerIfNecessary();
 
2438
                        }
 
2439
                }
 
2440
 
 
2441
                /**
 
2442
                 * Sets the scrollbar value based on the specified mouse event.
 
2443
                 * 
 
2444
                 * @param e
 
2445
                 *            Mouse event.
 
2446
                 */
 
2447
                private void setValueFrom(MouseEvent e) {
 
2448
                        boolean active = SubstanceScrollBarUI.this.isThumbRollover();
 
2449
                        BoundedRangeModel model = SubstanceScrollBarUI.this.scrollbar
 
2450
                                        .getModel();
 
2451
                        Rectangle thumbR = SubstanceScrollBarUI.this.getThumbBounds();
 
2452
                        int thumbMin = 0, thumbMax = 0, thumbPos;
 
2453
 
 
2454
                        ScrollPaneButtonPolicyKind buttonPolicy = SubstanceCoreUtilities
 
2455
                                        .getScrollPaneButtonsPolicyKind(SubstanceScrollBarUI.this.scrollbar);
 
2456
 
 
2457
                        if (SubstanceScrollBarUI.this.scrollbar.getOrientation() == JScrollBar.VERTICAL) {
 
2458
                                switch (buttonPolicy) {
 
2459
                                case OPPOSITE:
 
2460
                                        thumbMin = SubstanceScrollBarUI.this.decrButton.getY()
 
2461
                                                        + SubstanceScrollBarUI.this.decrButton.getHeight();
 
2462
                                        thumbMax = SubstanceScrollBarUI.this.incrButton.getY()
 
2463
                                                        - thumbR.height;
 
2464
                                        break;
 
2465
                                case ADJACENT:
 
2466
                                        thumbMin = 0;
 
2467
                                        thumbMax = SubstanceScrollBarUI.this.mySecondDecreaseButton
 
2468
                                                        .getY()
 
2469
                                                        - thumbR.height;
 
2470
                                        break;
 
2471
                                case NONE:
 
2472
                                        thumbMin = 0;
 
2473
                                        thumbMax = SubstanceScrollBarUI.this.scrollbar.getSize().height
 
2474
                                                        - SubstanceScrollBarUI.this.scrollbar.getInsets().bottom
 
2475
                                                        - thumbR.height;
 
2476
                                        break;
 
2477
                                case MULTIPLE:
 
2478
                                        thumbMin = SubstanceScrollBarUI.this.decrButton.getY()
 
2479
                                                        + SubstanceScrollBarUI.this.decrButton.getHeight();
 
2480
                                        thumbMax = SubstanceScrollBarUI.this.mySecondDecreaseButton
 
2481
                                                        .getY()
 
2482
                                                        - thumbR.height;
 
2483
                                        break;
 
2484
                                case MULTIPLE_BOTH:
 
2485
                                        thumbMin = SubstanceScrollBarUI.this.mySecondIncreaseButton
 
2486
                                                        .getY()
 
2487
                                                        + SubstanceScrollBarUI.this.mySecondIncreaseButton
 
2488
                                                                        .getHeight();
 
2489
                                        thumbMax = SubstanceScrollBarUI.this.mySecondDecreaseButton
 
2490
                                                        .getY()
 
2491
                                                        - thumbR.height;
 
2492
                                        break;
 
2493
                                }
 
2494
 
 
2495
                                thumbPos = Math.min(thumbMax, Math.max(thumbMin,
 
2496
                                                (e.getY() - this.offset)));
 
2497
                                SubstanceScrollBarUI.this.setThumbBounds(thumbR.x, thumbPos,
 
2498
                                                thumbR.width, thumbR.height);
 
2499
                        } else {
 
2500
                                if (SubstanceScrollBarUI.this.scrollbar
 
2501
                                                .getComponentOrientation().isLeftToRight()) {
 
2502
                                        switch (buttonPolicy) {
 
2503
                                        case OPPOSITE:
 
2504
                                                thumbMin = SubstanceScrollBarUI.this.decrButton.getX()
 
2505
                                                                + SubstanceScrollBarUI.this.decrButton
 
2506
                                                                                .getWidth();
 
2507
                                                thumbMax = SubstanceScrollBarUI.this.incrButton.getX()
 
2508
                                                                - thumbR.width;
 
2509
                                                break;
 
2510
                                        case ADJACENT:
 
2511
                                                thumbMin = 0;
 
2512
                                                thumbMax = SubstanceScrollBarUI.this.mySecondDecreaseButton
 
2513
                                                                .getX()
 
2514
                                                                - thumbR.width;
 
2515
                                                break;
 
2516
                                        case MULTIPLE:
 
2517
                                                thumbMin = SubstanceScrollBarUI.this.decrButton.getX()
 
2518
                                                                + SubstanceScrollBarUI.this.decrButton
 
2519
                                                                                .getWidth();
 
2520
                                                thumbMax = SubstanceScrollBarUI.this.mySecondDecreaseButton
 
2521
                                                                .getX()
 
2522
                                                                - thumbR.width;
 
2523
                                                break;
 
2524
                                        case MULTIPLE_BOTH:
 
2525
                                                thumbMin = SubstanceScrollBarUI.this.mySecondIncreaseButton
 
2526
                                                                .getX()
 
2527
                                                                + SubstanceScrollBarUI.this.mySecondIncreaseButton
 
2528
                                                                                .getWidth();
 
2529
                                                thumbMax = SubstanceScrollBarUI.this.mySecondDecreaseButton
 
2530
                                                                .getX()
 
2531
                                                                - thumbR.width;
 
2532
                                                break;
 
2533
                                        case NONE:
 
2534
                                                thumbMin = 0;
 
2535
                                                thumbMax = SubstanceScrollBarUI.this.scrollbar
 
2536
                                                                .getSize().width
 
2537
                                                                - SubstanceScrollBarUI.this.scrollbar
 
2538
                                                                                .getInsets().right - thumbR.width;
 
2539
                                                break;
 
2540
                                        }
 
2541
                                } else {
 
2542
                                        switch (buttonPolicy) {
 
2543
                                        case OPPOSITE:
 
2544
                                                thumbMin = SubstanceScrollBarUI.this.incrButton.getX()
 
2545
                                                                + SubstanceScrollBarUI.this.incrButton
 
2546
                                                                                .getWidth();
 
2547
                                                thumbMax = SubstanceScrollBarUI.this.decrButton.getX()
 
2548
                                                                - thumbR.width;
 
2549
                                                break;
 
2550
                                        case ADJACENT:
 
2551
                                                thumbMin = SubstanceScrollBarUI.this.mySecondDecreaseButton
 
2552
                                                                .getX()
 
2553
                                                                + SubstanceScrollBarUI.this.mySecondDecreaseButton
 
2554
                                                                                .getWidth();
 
2555
                                                thumbMax = SubstanceScrollBarUI.this.scrollbar
 
2556
                                                                .getSize().width
 
2557
                                                                - SubstanceScrollBarUI.this.scrollbar
 
2558
                                                                                .getInsets().right - thumbR.width;
 
2559
                                                break;
 
2560
                                        case MULTIPLE:
 
2561
                                                thumbMin = SubstanceScrollBarUI.this.mySecondDecreaseButton
 
2562
                                                                .getX()
 
2563
                                                                + SubstanceScrollBarUI.this.mySecondDecreaseButton
 
2564
                                                                                .getWidth();
 
2565
                                                thumbMax = SubstanceScrollBarUI.this.decrButton.getX()
 
2566
                                                                - thumbR.width;
 
2567
                                                break;
 
2568
                                        case MULTIPLE_BOTH:
 
2569
                                                thumbMin = SubstanceScrollBarUI.this.mySecondDecreaseButton
 
2570
                                                                .getX()
 
2571
                                                                + SubstanceScrollBarUI.this.mySecondDecreaseButton
 
2572
                                                                                .getWidth();
 
2573
                                                thumbMax = SubstanceScrollBarUI.this.mySecondIncreaseButton
 
2574
                                                                .getX()
 
2575
                                                                - thumbR.width;
 
2576
                                                break;
 
2577
                                        case NONE:
 
2578
                                                thumbMin = 0;
 
2579
                                                thumbMax = SubstanceScrollBarUI.this.scrollbar
 
2580
                                                                .getSize().width
 
2581
                                                                - SubstanceScrollBarUI.this.scrollbar
 
2582
                                                                                .getInsets().right - thumbR.width;
 
2583
                                                break;
 
2584
                                        }
 
2585
                                }
 
2586
                                // System.out.println(thumbMin + " : " + thumbMax + " : "
 
2587
                                // + (e.getX() - offset));
 
2588
                                thumbPos = Math.min(thumbMax, Math.max(thumbMin,
 
2589
                                                (e.getX() - this.offset)));
 
2590
                                SubstanceScrollBarUI.this.setThumbBounds(thumbPos, thumbR.y,
 
2591
                                                thumbR.width, thumbR.height);
 
2592
                        }
 
2593
 
 
2594
                        /*
 
2595
                         * Set the scrollbars value. If the thumb has reached the end of the
 
2596
                         * scrollbar, then just set the value to its maximum. Otherwise
 
2597
                         * compute the value as accurately as possible.
 
2598
                         */
 
2599
                        if (thumbPos == thumbMax) {
 
2600
                                if (SubstanceScrollBarUI.this.scrollbar.getOrientation() == JScrollBar.VERTICAL
 
2601
                                                || SubstanceScrollBarUI.this.scrollbar
 
2602
                                                                .getComponentOrientation().isLeftToRight()) {
 
2603
                                        SubstanceScrollBarUI.this.scrollbar.setValue(model
 
2604
                                                        .getMaximum()
 
2605
                                                        - model.getExtent());
 
2606
                                } else {
 
2607
                                        SubstanceScrollBarUI.this.scrollbar.setValue(model
 
2608
                                                        .getMinimum());
 
2609
                                }
 
2610
                        } else {
 
2611
                                float valueMax = model.getMaximum() - model.getExtent();
 
2612
                                float valueRange = valueMax - model.getMinimum();
 
2613
                                float thumbValue = thumbPos - thumbMin;
 
2614
                                float thumbRange = thumbMax - thumbMin;
 
2615
                                int value;
 
2616
                                if (SubstanceScrollBarUI.this.scrollbar.getOrientation() == JScrollBar.VERTICAL
 
2617
                                                || SubstanceScrollBarUI.this.scrollbar
 
2618
                                                                .getComponentOrientation().isLeftToRight()) {
 
2619
                                        value = (int) (0.5 + ((thumbValue / thumbRange) * valueRange));
 
2620
                                } else {
 
2621
                                        value = (int) (0.5 + (((thumbMax - thumbPos) / thumbRange) * valueRange));
 
2622
                                }
 
2623
 
 
2624
                                SubstanceScrollBarUI.this.scrollbar.setValue(value
 
2625
                                                + model.getMinimum());
 
2626
                        }
 
2627
                        SubstanceScrollBarUI.this.setThumbRollover(active);
 
2628
                }
 
2629
 
 
2630
                /**
 
2631
                 * If necessary, starts the scroll timer.
 
2632
                 */
 
2633
                private void startScrollTimerIfNecessary() {
 
2634
                        if (SubstanceScrollBarUI.this.scrollTimer.isRunning()) {
 
2635
                                return;
 
2636
                        }
 
2637
                        switch (SubstanceScrollBarUI.this.scrollbar.getOrientation()) {
 
2638
                        case JScrollBar.VERTICAL:
 
2639
                                if (this.direction > 0) {
 
2640
                                        if (SubstanceScrollBarUI.this.getThumbBounds().y
 
2641
                                                        + SubstanceScrollBarUI.this.getThumbBounds().height < ((SubstanceTrackListener) SubstanceScrollBarUI.this.trackListener).currentMouseY) {
 
2642
                                                SubstanceScrollBarUI.this.scrollTimer.start();
 
2643
                                        }
 
2644
                                } else if (SubstanceScrollBarUI.this.getThumbBounds().y > ((SubstanceTrackListener) SubstanceScrollBarUI.this.trackListener).currentMouseY) {
 
2645
                                        SubstanceScrollBarUI.this.scrollTimer.start();
 
2646
                                }
 
2647
                                break;
 
2648
                        case JScrollBar.HORIZONTAL:
 
2649
                                if (this.direction > 0) {
 
2650
                                        if (SubstanceScrollBarUI.this.getThumbBounds().x
 
2651
                                                        + SubstanceScrollBarUI.this.getThumbBounds().width < ((SubstanceTrackListener) SubstanceScrollBarUI.this.trackListener).currentMouseX) {
 
2652
                                                SubstanceScrollBarUI.this.scrollTimer.start();
 
2653
                                        }
 
2654
                                } else if (SubstanceScrollBarUI.this.getThumbBounds().x > ((SubstanceTrackListener) SubstanceScrollBarUI.this.trackListener).currentMouseX) {
 
2655
                                        SubstanceScrollBarUI.this.scrollTimer.start();
 
2656
                                }
 
2657
                                break;
 
2658
                        }
 
2659
                }
 
2660
 
 
2661
                /*
 
2662
                 * (non-Javadoc)
 
2663
                 * 
 
2664
                 * @see
 
2665
                 * javax.swing.plaf.basic.BasicScrollBarUI$TrackListener#mouseMoved(
 
2666
                 * java.awt.event.MouseEvent)
 
2667
                 */
 
2668
                @Override
 
2669
                public void mouseMoved(MouseEvent e) {
 
2670
                        if (!SubstanceScrollBarUI.this.isDragging) {
 
2671
                                SubstanceScrollBarUI.this.updateThumbState(e.getX(), e.getY());
 
2672
                        }
 
2673
                }
 
2674
 
 
2675
                /*
 
2676
                 * (non-Javadoc)
 
2677
                 * 
 
2678
                 * @see
 
2679
                 * javax.swing.plaf.basic.BasicScrollBarUI$TrackListener#mouseExited
 
2680
                 * (java.awt.event.MouseEvent)
 
2681
                 */
 
2682
                @Override
 
2683
                public void mouseExited(MouseEvent e) {
 
2684
                        if (!SubstanceScrollBarUI.this.isDragging) {
 
2685
                                SubstanceScrollBarUI.this.setThumbRollover(false);
 
2686
                        }
 
2687
                }
 
2688
        }
 
2689
 
 
2690
        /*
 
2691
         * (non-Javadoc)
 
2692
         * 
 
2693
         * @see javax.swing.plaf.basic.BasicScrollBarUI#createArrowButtonListener()
 
2694
         */
 
2695
        @Override
 
2696
        protected ArrowButtonListener createArrowButtonListener() {
 
2697
                return new SubstanceArrowButtonListener();
 
2698
        }
 
2699
 
 
2700
        /**
 
2701
         * Listener on arrow buttons. Need to override the super implementation for
 
2702
         * the {@link ScrollPaneButtonPolicyKind#MULTIPLE_BOTH} policy.
 
2703
         * 
 
2704
         * @author Kirill Grouchnikov
 
2705
         */
 
2706
        protected class SubstanceArrowButtonListener extends ArrowButtonListener {
 
2707
                /**
 
2708
                 * Because we are handling both mousePressed and Actions we need to make
 
2709
                 * sure we don't fire under both conditions. (keyfocus on scrollbars
 
2710
                 * causes action without mousePress
 
2711
                 */
 
2712
                boolean handledEvent;
 
2713
 
 
2714
                /*
 
2715
                 * (non-Javadoc)
 
2716
                 * 
 
2717
                 * @see
 
2718
                 * javax.swing.plaf.basic.BasicScrollBarUI$ArrowButtonListener#mousePressed
 
2719
                 * (java.awt.event.MouseEvent)
 
2720
                 */
 
2721
                @Override
 
2722
                public void mousePressed(MouseEvent e) {
 
2723
                        if (!SubstanceScrollBarUI.this.scrollbar.isEnabled()) {
 
2724
                                return;
 
2725
                        }
 
2726
                        // not an unmodified left mouse button
 
2727
                        // if(e.getModifiers() != InputEvent.BUTTON1_MASK) {return; }
 
2728
                        if (!SwingUtilities.isLeftMouseButton(e)) {
 
2729
                                return;
 
2730
                        }
 
2731
 
 
2732
                        int direction = ((e.getSource() == SubstanceScrollBarUI.this.incrButton) || (e
 
2733
                                        .getSource() == SubstanceScrollBarUI.this.mySecondIncreaseButton)) ? 1
 
2734
                                        : -1;
 
2735
 
 
2736
                        SubstanceScrollBarUI.this.scrollByUnit(direction);
 
2737
                        SubstanceScrollBarUI.this.scrollTimer.stop();
 
2738
                        SubstanceScrollBarUI.this.scrollListener.setDirection(direction);
 
2739
                        SubstanceScrollBarUI.this.scrollListener.setScrollByBlock(false);
 
2740
                        SubstanceScrollBarUI.this.scrollTimer.start();
 
2741
 
 
2742
                        this.handledEvent = true;
 
2743
                        if (!SubstanceScrollBarUI.this.scrollbar.hasFocus()
 
2744
                                        && SubstanceScrollBarUI.this.scrollbar
 
2745
                                                        .isRequestFocusEnabled()) {
 
2746
                                SubstanceScrollBarUI.this.scrollbar.requestFocus();
 
2747
                        }
 
2748
                }
 
2749
 
 
2750
                /*
 
2751
                 * (non-Javadoc)
 
2752
                 * 
 
2753
                 * @see
 
2754
                 * javax.swing.plaf.basic.BasicScrollBarUI$ArrowButtonListener#mouseReleased
 
2755
                 * (java.awt.event.MouseEvent)
 
2756
                 */
 
2757
                @Override
 
2758
                public void mouseReleased(MouseEvent e) {
 
2759
                        SubstanceScrollBarUI.this.scrollTimer.stop();
 
2760
                        this.handledEvent = false;
 
2761
                        SubstanceScrollBarUI.this.scrollbar.setValueIsAdjusting(false);
 
2762
                }
 
2763
        }
 
2764
 
 
2765
        /**
 
2766
         * Updates the thumb state based on the coordinates.
 
2767
         * 
 
2768
         * @param x
 
2769
         *            X coordinate.
 
2770
         * @param y
 
2771
         *            Y coordinate.
 
2772
         */
 
2773
        private void updateThumbState(int x, int y) {
 
2774
                Rectangle rect = this.getThumbBounds();
 
2775
 
 
2776
                this.setThumbRollover(rect.contains(x, y));
 
2777
        }
 
2778
 
 
2779
        /*
 
2780
         * (non-Javadoc)
 
2781
         * 
 
2782
         * @see
 
2783
         * javax.swing.plaf.basic.BasicScrollBarUI#getPreferredSize(javax.swing.
 
2784
         * JComponent)
 
2785
         */
 
2786
        @Override
 
2787
        public Dimension getPreferredSize(JComponent c) {
 
2788
                if (scrollbar.getOrientation() == JScrollBar.VERTICAL) {
 
2789
                        return new Dimension(scrollBarWidth, Math.max(48,
 
2790
                                        5 * scrollBarWidth));
 
2791
                } else {
 
2792
                        return new Dimension(Math.max(48, 5 * scrollBarWidth),
 
2793
                                        scrollBarWidth);
 
2794
                }
 
2795
        }
 
2796
 
 
2797
        /**
 
2798
         * Composite button model that tracks changes to one primary and any number
 
2799
         * of secondary button models for composite rollover effects. This model can
 
2800
         * be used to "simulate" rollover effects on the primary component when the
 
2801
         * actual rollover happens on one of the secondary components. An example is
 
2802
         * a scroll bar. When the mouse enters one of the scroll buttons, the scroll
 
2803
         * track is highlighted as well.
 
2804
         * 
 
2805
         * @author Kirill Grouchnikov
 
2806
         */
 
2807
        private class CompositeButtonModel extends DefaultButtonModel {
 
2808
                /**
 
2809
                 * The primary model.
 
2810
                 */
 
2811
                protected ButtonModel primaryModel;
 
2812
 
 
2813
                /**
 
2814
                 * The secondary models.
 
2815
                 */
 
2816
                protected ButtonModel[] secondaryModels;
 
2817
 
 
2818
                protected ChangeListener listener;
 
2819
 
 
2820
                /**
 
2821
                 * Creates a new composite button model.
 
2822
                 * 
 
2823
                 * @param primaryModel
 
2824
                 *            The primary model.
 
2825
                 * @param secondaryButtons
 
2826
                 *            The secondary buttons.
 
2827
                 */
 
2828
                public CompositeButtonModel(ButtonModel primaryModel,
 
2829
                                AbstractButton... secondaryButtons) {
 
2830
                        this.primaryModel = primaryModel;
 
2831
                        List<ButtonModel> bmList = new LinkedList<ButtonModel>();
 
2832
                        for (AbstractButton secondary : secondaryButtons) {
 
2833
                                if (secondary != null) {
 
2834
                                        bmList.add(secondary.getModel());
 
2835
                                }
 
2836
                        }
 
2837
                        this.secondaryModels = bmList.toArray(new ButtonModel[0]);
 
2838
 
 
2839
                        this.listener = new ChangeListener() {
 
2840
                                @Override
 
2841
                                public void stateChanged(ChangeEvent e) {
 
2842
                                        syncModels();
 
2843
                                }
 
2844
                        };
 
2845
                        syncModels();
 
2846
                }
 
2847
 
 
2848
                private void syncModels() {
 
2849
                        this.setEnabled(this.primaryModel.isEnabled());
 
2850
                        this.setSelected(this.primaryModel.isSelected());
 
2851
 
 
2852
                        boolean isArmed = this.primaryModel.isArmed();
 
2853
                        for (ButtonModel secondary : this.secondaryModels) {
 
2854
                                isArmed = isArmed || secondary.isArmed();
 
2855
                        }
 
2856
                        this.setArmed(isArmed);
 
2857
 
 
2858
                        boolean isPressed = this.primaryModel.isPressed();
 
2859
                        for (ButtonModel secondary : this.secondaryModels) {
 
2860
                                isPressed = isPressed || secondary.isPressed();
 
2861
                        }
 
2862
                        this.setPressed(isPressed);
 
2863
 
 
2864
                        boolean isRollover = this.primaryModel.isRollover();
 
2865
                        for (ButtonModel secondary : this.secondaryModels) {
 
2866
                                isRollover = isRollover || secondary.isRollover();
 
2867
                        }
 
2868
                        this.setRollover(isRollover);
 
2869
                }
 
2870
 
 
2871
                public void registerListeners() {
 
2872
                        this.primaryModel.addChangeListener(this.listener);
 
2873
                        for (ButtonModel secondary : this.secondaryModels) {
 
2874
                                secondary.addChangeListener(this.listener);
 
2875
                        }
 
2876
                }
 
2877
 
 
2878
                public void unregisterListeners() {
 
2879
                        this.primaryModel.removeChangeListener(this.listener);
 
2880
                        for (ButtonModel secondary : this.secondaryModels) {
 
2881
                                secondary.removeChangeListener(this.listener);
 
2882
                        }
 
2883
                        this.listener = null;
 
2884
                }
 
2885
        }
 
2886
}
 
 
b'\\ No newline at end of file'