2
* Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
4
* Redistribution and use in source and binary forms, with or without
5
* modification, are permitted provided that the following conditions are met:
7
* o Redistributions of source code must retain the above copyright notice,
8
* this list of conditions and the following disclaimer.
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.
14
* o Neither the name of Laf-Widget 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.
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.
30
package org.pushingpixels.lafwidget.scroll;
33
import java.awt.event.*;
34
import java.awt.geom.Area;
35
import java.awt.image.BufferedImage;
36
import java.beans.PropertyChangeEvent;
37
import java.beans.PropertyChangeListener;
40
import javax.swing.event.MouseInputAdapter;
41
import javax.swing.event.MouseInputListener;
43
import org.pushingpixels.lafwidget.LafWidgetRepository;
44
import org.pushingpixels.lafwidget.LafWidgetUtilities2;
45
import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
46
import org.pushingpixels.lafwidget.preview.PreviewPainter;
47
import org.pushingpixels.trident.Timeline;
48
import org.pushingpixels.trident.Timeline.TimelineState;
49
import org.pushingpixels.trident.callback.UIThreadTimelineCallbackAdapter;
52
* ScrollPaneSelector is a little utility class that provides a means to quickly
53
* scroll both vertically and horizontally on a single mouse click, by dragging
54
* a selection rectangle over a "thumbnail" of the scrollPane's viewport view.
56
* Once the selector is installed on a given JScrollPane instance, a little
57
* button appears as soon as at least one of its scroll bars is made visible.
59
* Contributed by the original author under BSD license. Also appears in the <a
60
* href="https://jdnc-incubator.dev.java.net">JDNC Incubator</a>.
62
* @author weebib (Pierre LE LANNIC)
63
* @author Kirill Grouchnikov (animations).
65
public class ScrollPaneSelector extends JComponent {
66
// static final fields
67
private static final double MAX_SIZE = 200;
68
// private static final Icon LAUNCH_SELECTOR_ICON = new Icon() {
69
// public void paintIcon(Component c, Graphics g, int x, int y) {
70
// Color tmpColor = g.getColor();
71
// g.setColor(Color.BLACK);
72
// g.drawRect(2, 2, 10, 10);
73
// g.drawRect(4, 5, 6, 4);
74
// g.setColor(tmpColor);
77
// public int getIconWidth() {
81
// public int getIconHeight() {
85
// private static Map theInstalledScrollPaneSelectors = new HashMap();
86
private static final String COMPONENT_ORIENTATION = "componentOrientation";
88
// private static final String HAS_BEEN_UNINSTALLED =
89
// "lafwidget.internal.scrollPaneSelector.hasBeenUninstalled";
92
private LayoutManager theFormerLayoutManager;
93
private JScrollPane theScrollPane;
94
private JComponent theComponent;
95
private JPopupMenu thePopupMenu;
96
private boolean toRestoreOriginal;
97
private JButton theButton;
98
private BufferedImage theImage;
99
private Rectangle theStartRectangle;
100
private Rectangle theRectangle;
101
private Point theStartPoint;
102
private Point thePrevPoint;
103
private double theScale;
104
private PropertyChangeListener propertyChangeListener;
105
private ContainerAdapter theViewPortViewListener;
107
// -- Constructor ------
108
ScrollPaneSelector() {
109
setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
110
theScrollPane = null;
112
theStartRectangle = null;
114
theStartPoint = null;
116
theButton = new JButton();
117
LafWidgetRepository.getRepository().getLafSupport().markButtonAsFlat(
119
theButton.setFocusable(false);
120
theButton.setFocusPainted(false);
122
MouseInputListener mil = new MouseInputAdapter() {
124
public void mousePressed(MouseEvent e) {
125
Point p = e.getPoint();
126
SwingUtilities.convertPointToScreen(p, theButton);
131
public void mouseReleased(MouseEvent e) {
132
if (!thePopupMenu.isVisible())
134
toRestoreOriginal = false;
135
thePopupMenu.setVisible(false);
136
theStartRectangle = theRectangle;
140
public void mouseDragged(MouseEvent e) {
141
if (theStartPoint == null)
144
if (!thePopupMenu.isShowing())
147
Point newPoint = e.getPoint();
148
SwingUtilities.convertPointToScreen(newPoint, (Component) e
151
Rectangle popupScreenRect = new Rectangle(thePopupMenu
152
.getLocationOnScreen(), thePopupMenu.getSize());
153
if (!popupScreenRect.contains(newPoint))
156
int deltaX = (int) ((newPoint.x - thePrevPoint.x) / theScale);
157
int deltaY = (int) ((newPoint.y - thePrevPoint.y) / theScale);
158
scroll(deltaX, deltaY, false);
160
thePrevPoint = newPoint;
163
theButton.addMouseListener(mil);
164
theButton.addMouseMotionListener(mil);
165
setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
166
thePopupMenu = new JPopupMenu();
167
thePopupMenu.setLayout(new BorderLayout());
168
thePopupMenu.add(this, BorderLayout.CENTER);
169
propertyChangeListener = new PropertyChangeListener() {
171
public void propertyChange(PropertyChangeEvent evt) {
172
if (theScrollPane == null)
174
if ("componentOrientation".equals(evt.getPropertyName())) {
175
theScrollPane.setCorner(JScrollPane.LOWER_LEADING_CORNER,
177
theScrollPane.setCorner(JScrollPane.LOWER_TRAILING_CORNER,
182
theViewPortViewListener = new ContainerAdapter() {
184
public void componentAdded(ContainerEvent e) {
185
if (thePopupMenu.isVisible())
186
thePopupMenu.setVisible(false);
187
Component comp = theScrollPane.getViewport().getView();
188
theComponent = (comp instanceof JComponent) ? (JComponent) comp
192
thePopupMenu.addPropertyChangeListener(new PropertyChangeListener() {
194
public void propertyChange(PropertyChangeEvent evt) {
195
if ("visible".equals(evt.getPropertyName())) {
196
if (!thePopupMenu.isVisible()) {
198
.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
199
if (toRestoreOriginal) {
200
int deltaX = (int) ((thePrevPoint.x - theStartPoint.x) / theScale);
201
int deltaY = (int) ((thePrevPoint.y - theStartPoint.y) / theScale);
202
scroll(-deltaX, -deltaY, true);
210
// -- JComponent overriden methods ------
212
public Dimension getPreferredSize() {
213
if (theImage == null || theRectangle == null)
214
return new Dimension();
215
Insets insets = getInsets();
216
return new Dimension(theImage.getWidth(null) + insets.left
217
+ insets.right, theImage.getHeight(null) + insets.top
222
protected void paintComponent(Graphics g1D) {
223
if (theImage == null || theRectangle == null)
225
Graphics2D g = (Graphics2D) g1D.create();
227
Insets insets = getInsets();
228
int xOffset = insets.left;
229
int yOffset = insets.top;
230
int availableWidth = getWidth() - insets.left - insets.right;
231
int availableHeight = getHeight() - insets.top - insets.bottom;
232
g.drawImage(theImage, xOffset, yOffset, null);
234
Color tmpColor = g.getColor();
235
Area area = new Area(new Rectangle(xOffset, yOffset, availableWidth,
237
area.subtract(new Area(theRectangle));
238
g.setColor(new Color(200, 200, 200, 128));
240
g.setColor(Color.BLACK);
241
g.draw(theRectangle);
242
g.setColor(tmpColor);
247
// -- Private methods ------
248
void installOnScrollPane(JScrollPane aScrollPane) {
249
if (theScrollPane != null)
250
uninstallFromScrollPane();
251
theScrollPane = aScrollPane;
252
theFormerLayoutManager = theScrollPane.getLayout();
253
theScrollPane.setLayout(new TweakedScrollPaneLayout());
254
theScrollPane.firePropertyChange("layoutManager", false, true);
255
theScrollPane.addPropertyChangeListener(COMPONENT_ORIENTATION,
256
propertyChangeListener);
257
theScrollPane.getViewport().addContainerListener(
258
theViewPortViewListener);
259
theScrollPane.setCorner(JScrollPane.LOWER_TRAILING_CORNER, theButton);
260
Component comp = theScrollPane.getViewport().getView();
261
theComponent = (comp instanceof JComponent) ? (JComponent) comp : null;
263
this.theButton.setIcon(LafWidgetRepository.getRepository()
264
.getLafSupport().getSearchIcon(
265
UIManager.getInt("ScrollBar.width") - 3,
266
theScrollPane.getComponentOrientation()));
268
theScrollPane.doLayout();
271
void uninstallFromScrollPane() {
272
if (theScrollPane == null)
274
if (thePopupMenu.isVisible())
275
thePopupMenu.setVisible(false);
276
theScrollPane.setCorner(JScrollPane.LOWER_TRAILING_CORNER, null);
277
theScrollPane.removePropertyChangeListener(COMPONENT_ORIENTATION,
278
propertyChangeListener);
279
theScrollPane.getViewport().removeContainerListener(
280
theViewPortViewListener);
281
theScrollPane.setLayout(theFormerLayoutManager);
282
theScrollPane.firePropertyChange("layoutManager", true, false);
283
theScrollPane = null;
286
private void display(Point aPointOnScreen) {
287
if (theComponent == null)
290
PreviewPainter previewPainter = LafWidgetUtilities2
291
.getComponentPreviewPainter(theScrollPane);
292
if (!previewPainter.hasPreview(theComponent.getParent(), theComponent,
296
// if (previewPainter == null) {
297
// double compWidth = theComponent.getWidth();
298
// double compHeight = theComponent.getHeight();
299
// double scaleX = MAX_SIZE / compWidth;
300
// double scaleY = MAX_SIZE / compHeight;
301
// theScale = Math.min(scaleX, scaleY);
302
// theImage = new BufferedImage(
303
// (int) (theComponent.getWidth() * theScale),
304
// (int) (theComponent.getHeight() * theScale),
305
// BufferedImage.TYPE_INT_RGB);
307
// Graphics2D g = theImage.createGraphics();
308
// g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
309
// RenderingHints.VALUE_INTERPOLATION_BILINEAR);
310
// g.scale(theScale, theScale);
311
// theComponent.paint(g);
314
Dimension pDimension = previewPainter.getPreviewWindowDimension(
315
theComponent.getParent(), theComponent, 0);
316
double compWidth = theComponent.getWidth();
317
double compHeight = theComponent.getHeight();
318
double scaleX = pDimension.getWidth() / compWidth;
319
double scaleY = pDimension.getHeight() / compHeight;
320
theScale = Math.min(scaleX, scaleY);
321
theImage = new BufferedImage(
322
(int) (theComponent.getWidth() * theScale), (int) (theComponent
323
.getHeight() * theScale), BufferedImage.TYPE_INT_RGB);
325
Graphics2D g = theImage.createGraphics();
326
previewPainter.previewComponent(null, theComponent, 0, g, 0, 0,
327
theImage.getWidth(), theImage.getHeight());
331
theStartRectangle = theComponent.getVisibleRect();
332
Insets insets = getInsets();
333
theStartRectangle.x = (int) (theScale * theStartRectangle.x + insets.left);
334
theStartRectangle.y = (int) (theScale * theStartRectangle.y + insets.right);
335
theStartRectangle.width *= theScale;
336
theStartRectangle.height *= theScale;
337
theRectangle = theStartRectangle;
339
Dimension pref = thePopupMenu.getPreferredSize();
340
Point buttonLocation = theButton.getLocationOnScreen();
341
Point popupLocation = new Point(
342
(theButton.getWidth() - pref.width) / 2,
343
(theButton.getHeight() - pref.height) / 2);
344
Point centerPoint = new Point(buttonLocation.x + popupLocation.x
345
+ theRectangle.x + theRectangle.width / 2, buttonLocation.y
346
+ popupLocation.y + theRectangle.y + theRectangle.height / 2);
348
// Attempt to move the mouse pointer to the center of the selector's
350
new Robot().mouseMove(centerPoint.x, centerPoint.y);
351
theStartPoint = centerPoint;
352
} catch (Exception e) {
353
// Since we cannot move the cursor, we'll move the popup instead.
354
theStartPoint = aPointOnScreen;
355
popupLocation.x += theStartPoint.x - centerPoint.x;
356
popupLocation.y += theStartPoint.y - centerPoint.y;
358
thePrevPoint = new Point(theStartPoint);
359
toRestoreOriginal = true;
360
thePopupMenu.show(theButton, popupLocation.x, popupLocation.y);
363
private void moveRectangle(int aDeltaX, int aDeltaY) {
364
if (theStartRectangle == null)
367
Insets insets = getInsets();
368
Rectangle newRect = new Rectangle(theStartRectangle);
369
newRect.x += aDeltaX;
370
newRect.y += aDeltaY;
371
newRect.x = Math.min(Math.max(newRect.x, insets.left), getWidth()
372
- insets.right - newRect.width);
373
newRect.y = Math.min(Math.max(newRect.y, insets.right), getHeight()
374
- insets.bottom - newRect.height);
375
Rectangle clip = new Rectangle();
376
Rectangle.union(theRectangle, newRect, clip);
378
theRectangle = newRect;
379
paintImmediately(clip);
382
private void syncRectangle() {
383
JViewport viewport = this.theScrollPane.getViewport();
384
Rectangle viewRect = viewport.getViewRect();
386
Insets insets = getInsets();
387
Rectangle newRect = new Rectangle();
388
newRect.x = (int) (theScale * viewRect.x + insets.left);
389
newRect.y = (int) (theScale * viewRect.y + insets.top);
390
newRect.width = (int) (viewRect.width * theScale);
391
newRect.height = (int) (viewRect.height * theScale);
393
Rectangle clip = new Rectangle();
394
Rectangle.union(theRectangle, newRect, clip);
396
theRectangle = newRect;
398
// System.out.println(viewRect + "-->" + theRectangle);
399
paintImmediately(clip);
402
private void scroll(final int aDeltaX, final int aDeltaY, boolean toAnimate) {
403
if (theComponent == null)
405
final Rectangle oldRectangle = theComponent.getVisibleRect();
406
final Rectangle newRectangle = new Rectangle(oldRectangle.x + aDeltaX,
407
oldRectangle.y + aDeltaY, oldRectangle.width,
408
oldRectangle.height);
412
Timeline scrollTimeline = new Timeline(theComponent);
413
AnimationConfigurationManager.getInstance().configureTimeline(
415
scrollTimeline.addCallback(new UIThreadTimelineCallbackAdapter() {
417
public void onTimelineStateChanged(TimelineState oldState,
418
TimelineState newState, float durationFraction,
419
float timelinePosition) {
420
if ((oldState == TimelineState.DONE)
421
&& (newState == TimelineState.IDLE)) {
422
theComponent.scrollRectToVisible(newRectangle);
428
public void onTimelinePulse(float durationFraction,
429
float timelinePosition) {
430
int x = (int) (oldRectangle.x + timelinePosition * aDeltaX);
431
int y = (int) (oldRectangle.y + timelinePosition * aDeltaY);
432
theComponent.scrollRectToVisible(new Rectangle(x, y,
433
oldRectangle.width, oldRectangle.height));
437
scrollTimeline.play();
439
theComponent.scrollRectToVisible(newRectangle);
b'\\ No newline at end of file'