29 import java.awt.event.*;
30 import java.awt.peer.ComponentPeer;
31 import java.beans.Transient;
32 import javax.swing.plaf.ViewportUI;
33
34 import javax.swing.event.*;
35 import javax.swing.border.*;
36 import javax.accessibility.*;
37
38 import java.io.Serializable;
39
40 import sun.awt.AWTAccessor;
41
42 /**
43 * The "viewport" or "porthole" through which you see the underlying
44 * information. When you scroll, what moves is the viewport. It is like
45 * peering through a camera's viewfinder. Moving the viewfinder upwards
46 * brings new things into view at the top of the picture and loses
47 * things that were at the bottom.
48 * <p>
49 * By default, <code>JViewport</code> is opaque. To change this, use the
50 * <code>setOpaque</code> method.
51 * <p>
52 * <b>NOTE:</b>We have implemented a faster scrolling algorithm that
53 * does not require a buffer to draw in. The algorithm works as follows:
54 * <ol><li>The view and parent view and checked to see if they are
55 * <code>JComponents</code>,
56 * if they aren't, stop and repaint the whole viewport.
57 * <li>If the viewport is obscured by an ancestor, stop and repaint the whole
58 * viewport.
59 * <li>Compute the region that will become visible, if it is as big as
60 * the viewport, stop and repaint the whole view region.
61 * <li>Obtain the ancestor <code>Window</code>'s graphics and
62 * do a <code>copyArea</code> on the scrolled region.
63 * <li>Message the view to repaint the newly visible region.
64 * <li>The next time paint is invoked on the viewport, if the clip region
65 * is smaller than the viewport size a timer is kicked off to repaint the
66 * whole region.
67 * </ol>
68 * In general this approach is much faster. Compared to the backing store
69 * approach this avoids the overhead of maintaining an offscreen buffer and
70 * having to do two <code>copyArea</code>s.
71 * Compared to the non backing store case this
72 * approach will greatly reduce the painted region.
73 * <p>
74 * This approach can cause slower times than the backing store approach
75 * when the viewport is obscured by another window, or partially offscreen.
76 * When another window
77 * obscures the viewport the copyArea will copy garbage and a
78 * paint event will be generated by the system to inform us we need to
79 * paint the newly exposed region. The only way to handle this is to
80 * repaint the whole viewport, which can cause slower performance than the
81 * backing store case. In most applications very rarely will the user be
82 * scrolling while the viewport is obscured by another window or offscreen,
83 * so this optimization is usually worth the performance hit when obscured.
84 * <p>
85 * <strong>Warning:</strong> Swing is not thread safe. For more
86 * information see <a
87 * href="package-summary.html#threading">Swing's Threading
88 * Policy</a>.
89 * <p>
90 * <strong>Warning:</strong>
91 * Serialized objects of this class will not be compatible with
92 * future Swing releases. The current serialization support is
93 * appropriate for short term storage or RMI between applications running
94 * the same version of Swing. As of 1.4, support for long term storage
95 * of all JavaBeans™
96 * has been added to the <code>java.beans</code> package.
97 * Please see {@link java.beans.XMLEncoder}.
98 *
99 * @author Hans Muller
100 * @author Philip Milne
101 * @see JScrollPane
102 * @since 1.2
103 */
104 @SuppressWarnings("serial") // Same-version serialization only
105 public class JViewport extends JComponent implements Accessible
106 {
107 /**
108 * @see #getUIClassID
109 * @see #readObject
110 */
111 private static final String uiClassID = "ViewportUI";
112
113 /** Property used to indicate window blitting should not be done.
114 */
115 static final Object EnableWindowBlit = "EnableWindowBlit";
116
117 /**
118 * True when the viewport dimensions have been determined.
119 * The default is false.
120 */
121 protected boolean isViewSizeSet = false;
122
123 /**
124 * The last <code>viewPosition</code> that we've painted, so we know how
125 * much of the backing store image is valid.
126 */
127 protected Point lastPaintPosition = null;
128
129 /**
130 * True when this viewport is maintaining an offscreen image of its
131 * contents, so that some scrolling can take place using fast "bit-blit"
132 * operations instead of by accessing the view object to construct the
133 * display. The default is <code>false</code>.
134 *
135 * @deprecated As of Java 2 platform v1.3
136 * @see #setScrollMode
137 */
138 @Deprecated
139 protected boolean backingStore = false;
140
141 /** The view image used for a backing store. */
142 protected transient Image backingStoreImage = null;
143
144 /**
145 * The <code>scrollUnderway</code> flag is used for components like
146 * <code>JList</code>. When the downarrow key is pressed on a
147 * <code>JList</code> and the selected
148 * cell is the last in the list, the <code>scrollpane</code> autoscrolls.
149 * Here, the old selected cell needs repainting and so we need
150 * a flag to make the viewport do the optimized painting
151 * only when there is an explicit call to
152 * <code>setViewPosition(Point)</code>.
153 * When <code>setBounds</code> is called through other routes,
154 * the flag is off and the view repaints normally. Another approach
155 * would be to remove this from the <code>JViewport</code>
156 * class and have the <code>JList</code> manage this case by using
157 * <code>setBackingStoreEnabled</code>. The default is
158 * <code>false</code>.
159 */
160 protected boolean scrollUnderway = false;
161
162 /*
163 * Listener that is notified each time the view changes size.
164 */
165 private ComponentListener viewListener = null;
166
167 /* Only one <code>ChangeEvent</code> is needed per
168 * <code>JViewport</code> instance since the
169 * event's only (read-only) state is the source property. The source
170 * of events generated here is always "this".
171 */
172 private transient ChangeEvent changeEvent = null;
173
174 /**
175 * Use <code>graphics.copyArea</code> to implement scrolling.
176 * This is the fastest for most applications.
177 *
178 * @see #setScrollMode
179 * @since 1.3
180 */
181 public static final int BLIT_SCROLL_MODE = 1;
182
183 /**
184 * Draws viewport contents into an offscreen image.
185 * This was previously the default mode for <code>JTable</code>.
186 * This mode may offer advantages over "blit mode"
187 * in some cases, but it requires a large chunk of extra RAM.
188 *
189 * @see #setScrollMode
190 * @since 1.3
191 */
192 public static final int BACKINGSTORE_SCROLL_MODE = 2;
193
194 /**
195 * This mode uses the very simple method of redrawing the entire
196 * contents of the scrollpane each time it is scrolled.
197 * This was the default behavior in Swing 1.0 and Swing 1.1.
198 * Either of the other two options will provide better performance
199 * in most cases.
200 *
201 * @see #setScrollMode
202 * @since 1.3
203 */
204 public static final int SIMPLE_SCROLL_MODE = 0;
205
218 // not guaranteed to receive the paint event before other mouse events,
219 // so we can not be sure we haven't already copied garbage a bunch of
220 // times to different parts of the view. For that reason when a blit
221 // happens and the Component is obscured (the check for obscurity
222 // is not supported on all platforms and is checked via ComponentPeer
223 // methods) the ivar repaintAll is set to true. When paint is received
224 // if repaintAll is true (we previously did a blit) it is set to
225 // false, and if the clip region is smaller than the viewport
226 // waitingForRepaint is set to true and a timer is started. When
227 // the timer fires if waitingForRepaint is true, repaint is invoked.
228 // In the mean time, if the view is asked to scroll and waitingForRepaint
229 // is true, a blit will not happen, instead the non-backing store case
230 // of scrolling will happen, which will reset waitingForRepaint.
231 // waitingForRepaint is set to false in paint when the clip rect is
232 // bigger (or equal) to the size of the viewport.
233 // A Timer is used instead of just a repaint as it appeared to offer
234 // better performance.
235
236
237 /**
238 * This is set to true in <code>setViewPosition</code>
239 * if doing a window blit and the viewport is obscured.
240 */
241 private transient boolean repaintAll;
242
243 /**
244 * This is set to true in paint, if <code>repaintAll</code>
245 * is true and the clip rectangle does not match the bounds.
246 * If true, and scrolling happens the
247 * repaint manager is not cleared which then allows for the repaint
248 * previously invoked to succeed.
249 */
250 private transient boolean waitingForRepaint;
251
252 /**
253 * Instead of directly invoking repaint, a <code>Timer</code>
254 * is started and when it fires, repaint is invoked.
255 */
256 private transient Timer repaintTimer;
257
258 /**
259 * Set to true in paintView when paint is invoked.
260 */
261 private transient boolean inBlitPaint;
262
263 /**
264 * Whether or not a valid view has been installed.
265 */
266 private boolean hasHadValidView;
267
268 /**
269 * When view is changed we have to synchronize scrollbar values
270 * with viewport (see the BasicScrollPaneUI#syncScrollPaneWithViewport method).
271 * This flag allows to invoke that method while ScrollPaneLayout#layoutContainer
272 * is running.
273 */
274 private boolean viewChanged;
275
276 /** Creates a <code>JViewport</code>. */
277 public JViewport() {
278 super();
279 setLayout(createLayoutManager());
280 setOpaque(true);
281 updateUI();
282 setInheritsPopupMenu(true);
283 }
284
285
286
287 /**
288 * Returns the L&F object that renders this component.
289 *
290 * @return a <code>ViewportUI</code> object
291 * @since 1.3
292 */
293 public ViewportUI getUI() {
294 return (ViewportUI)ui;
295 }
296
297
298 /**
299 * Sets the L&F object that renders this component.
300 *
301 * @param ui the <code>ViewportUI</code> L&F object
302 * @see UIDefaults#getUI
303 * @beaninfo
304 * bound: true
305 * hidden: true
306 * attribute: visualUpdate true
307 * description: The UI object that implements the Component's LookAndFeel.
308 * @since 1.3
309 */
310 public void setUI(ViewportUI ui) {
311 super.setUI(ui);
312 }
313
314
315 /**
316 * Resets the UI property to a value from the current look and feel.
317 *
318 * @see JComponent#updateUI
319 */
320 public void updateUI() {
321 setUI((ViewportUI)UIManager.getUI(this));
322 }
323
324
325 /**
326 * Returns a string that specifies the name of the L&F class
327 * that renders this component.
328 *
329 * @return the string "ViewportUI"
330 *
331 * @see JComponent#getUIClassID
332 * @see UIDefaults#getUI
333 */
334 public String getUIClassID() {
335 return uiClassID;
336 }
337
338
339 /**
340 * Sets the <code>JViewport</code>'s one lightweight child,
341 * which can be <code>null</code>.
342 * (Since there is only one child which occupies the entire viewport,
343 * the <code>constraints</code> and <code>index</code>
344 * arguments are ignored.)
345 *
346 * @param child the lightweight <code>child</code> of the viewport
347 * @param constraints the <code>constraints</code> to be respected
348 * @param index the index
349 * @see #setView
350 */
351 protected void addImpl(Component child, Object constraints, int index) {
352 setView(child);
353 }
354
355
356 /**
357 * Removes the <code>Viewport</code>s one lightweight child.
358 *
359 * @see #setView
360 */
361 public void remove(Component child) {
362 child.removeComponentListener(viewListener);
363 super.remove(child);
364 }
365
366 /**
367 * Scrolls the view so that <code>Rectangle</code>
368 * within the view becomes visible.
369 * <p>
370 * This attempts to validate the view before scrolling if the
371 * view is currently not valid - <code>isValid</code> returns false.
372 * To avoid excessive validation when the containment hierarchy is
373 * being created this will not validate if one of the ancestors does not
374 * have a peer, or there is no validate root ancestor, or one of the
375 * ancestors is not a <code>Window</code> or <code>Applet</code>.
376 * <p>
377 * Note that this method will not scroll outside of the
378 * valid viewport; for example, if <code>contentRect</code> is larger
379 * than the viewport, scrolling will be confined to the viewport's
380 * bounds.
381 *
382 * @param contentRect the <code>Rectangle</code> to display
383 * @see JComponent#isValidateRoot
384 * @see java.awt.Component#isValid
385 */
386 public void scrollRectToVisible(Rectangle contentRect) {
387 Component view = getView();
388
389 if (view == null) {
390 return;
391 } else {
392 if (!view.isValid()) {
393 // If the view is not valid, validate. scrollRectToVisible
394 // may fail if the view is not valid first, contentRect
395 // could be bigger than invalid size.
396 validateView();
397 }
398 int dx, dy;
399
400 dx = positionAdjustment(getWidth(), contentRect.width, contentRect.x);
401 dy = positionAdjustment(getHeight(), contentRect.height, contentRect.y);
402
448 // true, the backing store is blitted. This fails
449 // if between the time setViewPosition is invoked
450 // and paint is received another repaint is queued
451 // indicating part of the view is invalid. There
452 // is no way for JViewport to notice another
453 // repaint has occurred and it ends up blitting
454 // what is now a dirty region and the repaint is
455 // never delivered.
456 // It just so happens JTable encounters this
457 // behavior by way of scrollRectToVisible, for
458 // this reason scrollUnderway is set to false
459 // here, which effectively disables the backing
460 // store.
461 scrollUnderway = false;
462 }
463 }
464 }
465 }
466
467 /**
468 * Ascends the <code>Viewport</code>'s parents stopping when
469 * a component is found that returns
470 * <code>true</code> to <code>isValidateRoot</code>.
471 * If all the <code>Component</code>'s parents are visible,
472 * <code>validate</code> will then be invoked on it. The
473 * <code>RepaintManager</code> is then invoked with
474 * <code>removeInvalidComponent</code>. This
475 * is the synchronous version of a <code>revalidate</code>.
476 */
477 private void validateView() {
478 Component validateRoot = SwingUtilities.getValidateRoot(this, false);
479
480 if (validateRoot == null) {
481 return;
482 }
483
484 // Validate the root.
485 validateRoot.validate();
486
487 // And let the RepaintManager it does not have to validate from
488 // validateRoot anymore.
489 RepaintManager rm = RepaintManager.currentManager(this);
490
491 if (rm != null) {
492 rm.removeInvalidComponent((JComponent)validateRoot);
493 }
494 }
495
532 // ---- | -> |---- |
533 // +-----+ +-----+
534 if (childAt <= 0 && childWidth <= parentWidth) {
535 return -childAt;
536 }
537
538 // +-----+ +-----+
539 //-------- | -> --------|
540 // +-----+ +-----+
541 if (childAt < 0 && childWidth >= parentWidth) {
542 return -childAt + parentWidth - childWidth;
543 }
544
545 return 0;
546 }
547
548
549 /**
550 * The viewport "scrolls" its child (called the "view") by the
551 * normal parent/child clipping (typically the view is moved in
552 * the opposite direction of the scroll). A non-<code>null</code> border,
553 * or non-zero insets, isn't supported, to prevent the geometry
554 * of this component from becoming complex enough to inhibit
555 * subclassing. To create a <code>JViewport</code> with a border,
556 * add it to a <code>JPanel</code> that has a border.
557 * <p>Note: If <code>border</code> is non-<code>null</code>, this
558 * method will throw an exception as borders are not supported on
559 * a <code>JViewPort</code>.
560 *
561 * @param border the <code>Border</code> to set
562 * @exception IllegalArgumentException this method is not implemented
563 */
564 public final void setBorder(Border border) {
565 if (border != null) {
566 throw new IllegalArgumentException("JViewport.setBorder() not supported");
567 }
568 }
569
570
571 /**
572 * Returns the insets (border) dimensions as (0,0,0,0), since borders
573 * are not supported on a <code>JViewport</code>.
574 *
575 * @return a <code>Rectangle</code> of zero dimension and zero origin
576 * @see #setBorder
577 */
578 public final Insets getInsets() {
579 return new Insets(0, 0, 0, 0);
580 }
581
582 /**
583 * Returns an <code>Insets</code> object containing this
584 * <code>JViewport</code>s inset values. The passed-in
585 * <code>Insets</code> object will be reinitialized, and
586 * all existing values within this object are overwritten.
587 *
588 * @param insets the <code>Insets</code> object which can be reused
589 * @return this viewports inset values
590 * @see #getInsets
591 * @beaninfo
592 * expert: true
593 */
594 public final Insets getInsets(Insets insets) {
595 insets.left = insets.top = insets.right = insets.bottom = 0;
596 return insets;
597 }
598
599
600 private Graphics getBackingStoreGraphics(Graphics g) {
601 Graphics bsg = backingStoreImage.getGraphics();
602 bsg.setColor(g.getColor());
603 bsg.setFont(g.getFont());
604 bsg.setClip(g.getClipBounds());
605 return bsg;
606 }
607
608
611 try {
612 super.paint(bsg);
613 g.drawImage(backingStoreImage, 0, 0, this);
614 } finally {
615 bsg.dispose();
616 }
617 }
618
619 private void paintViaBackingStore(Graphics g, Rectangle oClip) {
620 Graphics bsg = getBackingStoreGraphics(g);
621 try {
622 super.paint(bsg);
623 g.setClip(oClip);
624 g.drawImage(backingStoreImage, 0, 0, this);
625 } finally {
626 bsg.dispose();
627 }
628 }
629
630 /**
631 * The <code>JViewport</code> overrides the default implementation of
632 * this method (in <code>JComponent</code>) to return false.
633 * This ensures
634 * that the drawing machinery will call the <code>Viewport</code>'s
635 * <code>paint</code>
636 * implementation rather than messaging the <code>JViewport</code>'s
637 * children directly.
638 *
639 * @return false
640 */
641 public boolean isOptimizedDrawingEnabled() {
642 return false;
643 }
644
645 /**
646 * Returns true if scroll mode is a {@code BACKINGSTORE_SCROLL_MODE} to cause
647 * painting to originate from {@code JViewport}, or one of its
648 * ancestors. Otherwise returns {@code false}.
649 *
650 * @return true if scroll mode is a {@code BACKINGSTORE_SCROLL_MODE}.
651 * @see JComponent#isPaintingOrigin()
652 */
653 protected boolean isPaintingOrigin() {
654 return scrollMode == BACKINGSTORE_SCROLL_MODE;
655 }
656
657
658 /**
659 * Only used by the paint method below.
660 */
661 private Point getViewLocation() {
662 Component view = getView();
663 if (view != null) {
664 return view.getLocation();
665 }
666 else {
667 return new Point(0,0);
668 }
669 }
670
671 /**
672 * Depending on whether the <code>backingStore</code> is enabled,
673 * either paint the image through the backing store or paint
674 * just the recently exposed part, using the backing store
675 * to "blit" the remainder.
676 * <blockquote>
677 * The term "blit" is the pronounced version of the PDP-10
678 * BLT (BLock Transfer) instruction, which copied a block of
679 * bits. (In case you were curious.)
680 * </blockquote>
681 *
682 * @param g the <code>Graphics</code> context within which to paint
683 */
684 public void paint(Graphics g)
685 {
686 int width = getWidth();
687 int height = getHeight();
688
689 if ((width <= 0) || (height <= 0)) {
690 return;
691 }
692
693 if (inBlitPaint) {
694 // We invoked paint as part of copyArea cleanup, let it through.
695 super.paint(g);
696 return;
697 }
698
699 if (repaintAll) {
700 repaintAll = false;
701 Rectangle clipB = g.getClipBounds();
702 if (clipB.width < getWidth() ||
804 // Paint the rest of the view; the part that has just been exposed.
805 Rectangle r = viewBounds.intersection(blitPaint);
806 bsg.setClip(r);
807 super.paint(bsg);
808
809 // Copy whole of the backing store to g.
810 g.drawImage(backingStoreImage, 0, 0, this);
811 } finally {
812 bsg.dispose();
813 }
814 }
815 }
816 }
817 lastPaintPosition = getViewLocation();
818 scrollUnderway = false;
819 }
820
821
822 /**
823 * Sets the bounds of this viewport. If the viewport's width
824 * or height has changed, fire a <code>StateChanged</code> event.
825 *
826 * @param x left edge of the origin
827 * @param y top edge of the origin
828 * @param w width in pixels
829 * @param h height in pixels
830 *
831 * @see JComponent#reshape(int, int, int, int)
832 */
833 @SuppressWarnings("deprecation")
834 public void reshape(int x, int y, int w, int h) {
835 boolean sizeChanged = (getWidth() != w) || (getHeight() != h);
836 if (sizeChanged) {
837 backingStoreImage = null;
838 }
839 super.reshape(x, y, w, h);
840 if (sizeChanged || viewChanged) {
841 viewChanged = false;
842
843 fireStateChanged();
844 }
861 * @see #BACKINGSTORE_SCROLL_MODE
862 * @see #SIMPLE_SCROLL_MODE
863 *
864 * @beaninfo
865 * bound: false
866 * description: Method of moving contents for incremental scrolls.
867 * enum: BLIT_SCROLL_MODE JViewport.BLIT_SCROLL_MODE
868 * BACKINGSTORE_SCROLL_MODE JViewport.BACKINGSTORE_SCROLL_MODE
869 * SIMPLE_SCROLL_MODE JViewport.SIMPLE_SCROLL_MODE
870 *
871 * @since 1.3
872 */
873 public void setScrollMode(int mode) {
874 scrollMode = mode;
875 backingStore = mode == BACKINGSTORE_SCROLL_MODE;
876 }
877
878 /**
879 * Returns the current scrolling mode.
880 *
881 * @return the <code>scrollMode</code> property
882 * @see #setScrollMode
883 * @since 1.3
884 */
885 public int getScrollMode() {
886 return scrollMode;
887 }
888
889 /**
890 * Returns <code>true</code> if this viewport is maintaining
891 * an offscreen image of its contents.
892 *
893 * @return <code>true</code> if <code>scrollMode</code> is
894 * <code>BACKINGSTORE_SCROLL_MODE</code>
895 *
896 * @deprecated As of Java 2 platform v1.3, replaced by
897 * <code>getScrollMode()</code>.
898 */
899 @Deprecated
900 public boolean isBackingStoreEnabled() {
901 return scrollMode == BACKINGSTORE_SCROLL_MODE;
902 }
903
904
905 /**
906 * If true if this viewport will maintain an offscreen
907 * image of its contents. The image is used to reduce the cost
908 * of small one dimensional changes to the <code>viewPosition</code>.
909 * Rather than repainting the entire viewport we use
910 * <code>Graphics.copyArea</code> to effect some of the scroll.
911 *
912 * @param enabled if true, maintain an offscreen backing store
913 *
914 * @deprecated As of Java 2 platform v1.3, replaced by
915 * <code>setScrollMode()</code>.
916 */
917 @Deprecated
918 public void setBackingStoreEnabled(boolean enabled) {
919 if (enabled) {
920 setScrollMode(BACKINGSTORE_SCROLL_MODE);
921 } else {
922 setScrollMode(BLIT_SCROLL_MODE);
923 }
924 }
925
926 private boolean isBlitting() {
927 Component view = getView();
928 return (scrollMode == BLIT_SCROLL_MODE) &&
929 (view instanceof JComponent) && view.isOpaque();
930 }
931
932
933 /**
934 * Returns the <code>JViewport</code>'s one child or <code>null</code>.
935 *
936 * @return the viewports child, or <code>null</code> if none exists
937 *
938 * @see #setView
939 */
940 public Component getView() {
941 return (getComponentCount() > 0) ? getComponent(0) : null;
942 }
943
944 /**
945 * Sets the <code>JViewport</code>'s one lightweight child
946 * (<code>view</code>), which can be <code>null</code>.
947 *
948 * @param view the viewport's new lightweight child
949 *
950 * @see #getView
951 */
952 public void setView(Component view) {
953
954 /* Remove the viewport's existing children, if any.
955 * Note that removeAll() isn't used here because it
956 * doesn't call remove() (which JViewport overrides).
957 */
958 int n = getComponentCount();
959 for(int i = n - 1; i >= 0; i--) {
960 remove(getComponent(i));
961 }
962
963 isViewSizeSet = false;
964
965 if (view != null) {
966 super.addImpl(view, null, -1);
971 if (hasHadValidView) {
972 // Only fire a change if a view has been installed.
973 fireStateChanged();
974 }
975 else if (view != null) {
976 hasHadValidView = true;
977 }
978
979 viewChanged = true;
980
981 revalidate();
982 repaint();
983 }
984
985
986 /**
987 * If the view's size hasn't been explicitly set, return the
988 * preferred size, otherwise return the view's current size.
989 * If there is no view, return 0,0.
990 *
991 * @return a <code>Dimension</code> object specifying the size of the view
992 */
993 public Dimension getViewSize() {
994 Component view = getView();
995
996 if (view == null) {
997 return new Dimension(0,0);
998 }
999 else if (isViewSizeSet) {
1000 return view.getSize();
1001 }
1002 else {
1003 return view.getPreferredSize();
1004 }
1005 }
1006
1007
1008 /**
1009 * Sets the size of the view. A state changed event will be fired.
1010 *
1011 * @param newSize a <code>Dimension</code> object specifying the new
1012 * size of the view
1013 */
1014 public void setViewSize(Dimension newSize) {
1015 Component view = getView();
1016 if (view != null) {
1017 Dimension oldSize = view.getSize();
1018 if (!newSize.equals(oldSize)) {
1019 // scrollUnderway will be true if this is invoked as the
1020 // result of a validate and setViewPosition was previously
1021 // invoked.
1022 scrollUnderway = false;
1023 view.setSize(newSize);
1024 isViewSizeSet = true;
1025 fireStateChanged();
1026 }
1027 }
1028 }
1029
1030 /**
1031 * Returns the view coordinates that appear in the upper left
1032 * hand corner of the viewport, or 0,0 if there's no view.
1033 *
1034 * @return a <code>Point</code> object giving the upper left coordinates
1035 */
1036 public Point getViewPosition() {
1037 Component view = getView();
1038 if (view != null) {
1039 Point p = view.getLocation();
1040 p.x = -p.x;
1041 p.y = -p.y;
1042 return p;
1043 }
1044 else {
1045 return new Point(0,0);
1046 }
1047 }
1048
1049
1050 /**
1051 * Sets the view coordinates that appear in the upper left
1052 * hand corner of the viewport, does nothing if there's no view.
1053 *
1054 * @param p a <code>Point</code> object giving the upper left coordinates
1055 */
1056 public void setViewPosition(Point p)
1057 {
1058 Component view = getView();
1059 if (view == null) {
1060 return;
1061 }
1062
1063 int oldX, oldY, x = p.x, y = p.y;
1064
1065 /* Collect the old x,y values for the views location
1066 * and do the song and dance to avoid allocating
1067 * a Rectangle object if we don't have to.
1068 */
1069 if (view instanceof JComponent) {
1070 JComponent c = (JComponent)view;
1071 oldX = c.getX();
1072 oldY = c.getY();
1073 }
1074 else {
1115 else {
1116 // The visible region is dirty, no point in doing copyArea
1117 view.setLocation(newX, newY);
1118 repaintAll = false;
1119 }
1120 }
1121 else {
1122 scrollUnderway = true;
1123 // This calls setBounds(), and then repaint().
1124 view.setLocation(newX, newY);
1125 repaintAll = false;
1126 }
1127 // we must validate the hierarchy to not break the hw/lw mixing
1128 revalidate();
1129 fireStateChanged();
1130 }
1131 }
1132
1133
1134 /**
1135 * Returns a rectangle whose origin is <code>getViewPosition</code>
1136 * and size is <code>getExtentSize</code>.
1137 * This is the visible part of the view, in view coordinates.
1138 *
1139 * @return a <code>Rectangle</code> giving the visible part of
1140 * the view using view coordinates.
1141 */
1142 public Rectangle getViewRect() {
1143 return new Rectangle(getViewPosition(), getExtentSize());
1144 }
1145
1146
1147 /**
1148 * Computes the parameters for a blit where the backing store image
1149 * currently contains <code>oldLoc</code> in the upper left hand corner
1150 * and we're scrolling to <code>newLoc</code>.
1151 * The parameters are modified
1152 * to return the values required for the blit.
1153 *
1154 * @param dx the horizontal delta
1155 * @param dy the vertical delta
1156 * @param blitFrom the <code>Point</code> we're blitting from
1157 * @param blitTo the <code>Point</code> we're blitting to
1158 * @param blitSize the <code>Dimension</code> of the area to blit
1159 * @param blitPaint the area to blit
1160 * @return true if the parameters are modified and we're ready to blit;
1161 * false otherwise
1162 */
1163 protected boolean computeBlit(
1164 int dx,
1165 int dy,
1166 Point blitFrom,
1167 Point blitTo,
1168 Dimension blitSize,
1169 Rectangle blitPaint)
1170 {
1171 int dxAbs = Math.abs(dx);
1172 int dyAbs = Math.abs(dy);
1173 Dimension extentSize = getExtentSize();
1174
1175 if ((dx == 0) && (dy != 0) && (dyAbs < extentSize.height)) {
1176 if (dy < 0) {
1177 blitFrom.y = -dy;
1178 blitTo.y = 0;
1210 blitPaint.y = blitFrom.y = blitTo.y = 0;
1211
1212 blitSize.width = extentSize.width - dxAbs;
1213 blitSize.height = extentSize.height;
1214
1215 blitPaint.width = dxAbs;
1216 blitPaint.height = extentSize.height;
1217
1218 return true;
1219 }
1220
1221 else {
1222 return false;
1223 }
1224 }
1225
1226
1227 /**
1228 * Returns the size of the visible part of the view in view coordinates.
1229 *
1230 * @return a <code>Dimension</code> object giving the size of the view
1231 */
1232 @Transient
1233 public Dimension getExtentSize() {
1234 return getSize();
1235 }
1236
1237
1238 /**
1239 * Converts a size in pixel coordinates to view coordinates.
1240 * Subclasses of viewport that support "logical coordinates"
1241 * will override this method.
1242 *
1243 * @param size a <code>Dimension</code> object using pixel coordinates
1244 * @return a <code>Dimension</code> object converted to view coordinates
1245 */
1246 public Dimension toViewCoordinates(Dimension size) {
1247 return new Dimension(size);
1248 }
1249
1250 /**
1251 * Converts a point in pixel coordinates to view coordinates.
1252 * Subclasses of viewport that support "logical coordinates"
1253 * will override this method.
1254 *
1255 * @param p a <code>Point</code> object using pixel coordinates
1256 * @return a <code>Point</code> object converted to view coordinates
1257 */
1258 public Point toViewCoordinates(Point p) {
1259 return new Point(p);
1260 }
1261
1262
1263 /**
1264 * Sets the size of the visible part of the view using view coordinates.
1265 *
1266 * @param newExtent a <code>Dimension</code> object specifying
1267 * the size of the view
1268 */
1269 public void setExtentSize(Dimension newExtent) {
1270 Dimension oldExtent = getExtentSize();
1271 if (!newExtent.equals(oldExtent)) {
1272 setSize(newExtent);
1273 fireStateChanged();
1274 }
1275 }
1276
1277 /**
1278 * A listener for the view.
1279 * <p>
1280 * <strong>Warning:</strong>
1281 * Serialized objects of this class will not be compatible with
1282 * future Swing releases. The current serialization support is
1283 * appropriate for short term storage or RMI between applications running
1284 * the same version of Swing. As of 1.4, support for long term storage
1285 * of all JavaBeans™
1286 * has been added to the <code>java.beans</code> package.
1287 * Please see {@link java.beans.XMLEncoder}.
1288 */
1289 @SuppressWarnings("serial") // Same-version serialization only
1290 protected class ViewListener extends ComponentAdapter implements Serializable
1291 {
1292 public void componentResized(ComponentEvent e) {
1293 fireStateChanged();
1294 revalidate();
1295 }
1296 }
1297
1298 /**
1299 * Creates a listener for the view.
1300 * @return a <code>ViewListener</code>
1301 */
1302 protected ViewListener createViewListener() {
1303 return new ViewListener();
1304 }
1305
1306
1307 /**
1308 * Subclassers can override this to install a different
1309 * layout manager (or <code>null</code>) in the constructor. Returns
1310 * the <code>LayoutManager</code> to install on the <code>JViewport</code>.
1311 *
1312 * @return a <code>LayoutManager</code>
1313 */
1314 protected LayoutManager createLayoutManager() {
1315 return ViewportLayout.SHARED_INSTANCE;
1316 }
1317
1318
1319 /**
1320 * Adds a <code>ChangeListener</code> to the list that is
1321 * notified each time the view's
1322 * size, position, or the viewport's extent size has changed.
1323 *
1324 * @param l the <code>ChangeListener</code> to add
1325 * @see #removeChangeListener
1326 * @see #setViewPosition
1327 * @see #setViewSize
1328 * @see #setExtentSize
1329 */
1330 public void addChangeListener(ChangeListener l) {
1331 listenerList.add(ChangeListener.class, l);
1332 }
1333
1334 /**
1335 * Removes a <code>ChangeListener</code> from the list that's notified each
1336 * time the views size, position, or the viewports extent size
1337 * has changed.
1338 *
1339 * @param l the <code>ChangeListener</code> to remove
1340 * @see #addChangeListener
1341 */
1342 public void removeChangeListener(ChangeListener l) {
1343 listenerList.remove(ChangeListener.class, l);
1344 }
1345
1346 /**
1347 * Returns an array of all the <code>ChangeListener</code>s added
1348 * to this JViewport with addChangeListener().
1349 *
1350 * @return all of the <code>ChangeListener</code>s added or an empty
1351 * array if no listeners have been added
1352 * @since 1.4
1353 */
1354 public ChangeListener[] getChangeListeners() {
1355 return listenerList.getListeners(ChangeListener.class);
1356 }
1357
1358 /**
1359 * Notifies all <code>ChangeListeners</code> when the views
1360 * size, position, or the viewports extent size has changed.
1361 *
1362 * @see #addChangeListener
1363 * @see #removeChangeListener
1364 * @see EventListenerList
1365 */
1366 protected void fireStateChanged()
1367 {
1368 Object[] listeners = listenerList.getListenerList();
1369 for (int i = listeners.length - 2; i >= 0; i -= 2) {
1370 if (listeners[i] == ChangeListener.class) {
1371 if (changeEvent == null) {
1372 changeEvent = new ChangeEvent(this);
1373 }
1374 ((ChangeListener)listeners[i + 1]).stateChanged(changeEvent);
1375 }
1376 }
1377 }
1378
1379 /**
1380 * Always repaint in the parents coordinate system to make sure
1381 * only one paint is performed by the <code>RepaintManager</code>.
1382 *
1383 * @param tm maximum time in milliseconds before update
1384 * @param x the <code>x</code> coordinate (pixels over from left)
1385 * @param y the <code>y</code> coordinate (pixels down from top)
1386 * @param w the width
1387 * @param h the height
1388 * @see java.awt.Component#update(java.awt.Graphics)
1389 */
1390 public void repaint(long tm, int x, int y, int w, int h) {
1391 Container parent = getParent();
1392 if(parent != null)
1393 parent.repaint(tm,x+getX(),y+getY(),w,h);
1394 else
1395 super.repaint(tm,x,y,w,h);
1396 }
1397
1398
1399 /**
1400 * Returns a string representation of this <code>JViewport</code>.
1401 * This method
1402 * is intended to be used only for debugging purposes, and the
1403 * content and format of the returned string may vary between
1404 * implementations. The returned string may be empty but may not
1405 * be <code>null</code>.
1406 *
1407 * @return a string representation of this <code>JViewport</code>
1408 */
1409 protected String paramString() {
1410 String isViewSizeSetString = (isViewSizeSet ?
1411 "true" : "false");
1412 String lastPaintPositionString = (lastPaintPosition != null ?
1413 lastPaintPosition.toString() : "");
1414 String scrollUnderwayString = (scrollUnderway ?
1415 "true" : "false");
1416
1417 return super.paramString() +
1418 ",isViewSizeSet=" + isViewSizeSetString +
1419 ",lastPaintPosition=" + lastPaintPositionString +
1420 ",scrollUnderway=" + scrollUnderwayString;
1421 }
1422
1423 //
1424 // Following is used when doBlit is true.
1425 //
1426
1427 /**
1428 * Notifies listeners of a property change. This is subclassed to update
1429 * the <code>windowBlit</code> property.
1430 * (The <code>putClientProperty</code> property is final).
1431 *
1432 * @param propertyName a string containing the property name
1433 * @param oldValue the old value of the property
1434 * @param newValue the new value of the property
1435 */
1436 protected void firePropertyChange(String propertyName, Object oldValue,
1437 Object newValue) {
1438 super.firePropertyChange(propertyName, oldValue, newValue);
1439 if (propertyName.equals(EnableWindowBlit)) {
1440 if (newValue != null) {
1441 setScrollMode(BLIT_SCROLL_MODE);
1442 } else {
1443 setScrollMode(SIMPLE_SCROLL_MODE);
1444 }
1445 }
1446 }
1447
1448 /**
1449 * Returns true if the component needs to be completely repainted after
1450 * a blit and a paint is received.
1478
1479 private Timer createRepaintTimer() {
1480 Timer timer = new Timer(300, new ActionListener() {
1481 public void actionPerformed(ActionEvent ae) {
1482 // waitingForRepaint will be false if a paint came down
1483 // with the complete clip rect, in which case we don't
1484 // have to cause a repaint.
1485 if (waitingForRepaint) {
1486 repaint();
1487 }
1488 }
1489 });
1490 timer.setRepeats(false);
1491 return timer;
1492 }
1493
1494 /**
1495 * If the repaint manager has a dirty region for the view, the view is
1496 * asked to paint.
1497 *
1498 * @param g the <code>Graphics</code> context within which to paint
1499 */
1500 private void flushViewDirtyRegion(Graphics g, Rectangle dirty) {
1501 JComponent view = (JComponent) getView();
1502 if(dirty != null && dirty.width > 0 && dirty.height > 0) {
1503 dirty.x += view.getX();
1504 dirty.y += view.getY();
1505 Rectangle clip = g.getClipBounds();
1506 if (clip == null) {
1507 // Only happens in 1.2
1508 g.setClip(0, 0, getWidth(), getHeight());
1509 }
1510 g.clipRect(dirty.x, dirty.y, dirty.width, dirty.height);
1511 clip = g.getClipBounds();
1512 // Only paint the dirty region if it is visible.
1513 if (clip.width > 0 && clip.height > 0) {
1514 paintView(g);
1515 }
1516 }
1517 }
1518
1519 /**
1520 * Used when blitting.
1521 *
1522 * @param g the <code>Graphics</code> context within which to paint
1523 * @return true if blitting succeeded; otherwise false
1524 */
1525 private boolean windowBlitPaint(Graphics g) {
1526 int width = getWidth();
1527 int height = getHeight();
1528
1529 if ((width == 0) || (height == 0)) {
1530 return false;
1531 }
1532
1533 boolean retValue;
1534 RepaintManager rm = RepaintManager.currentManager(this);
1535 JComponent view = (JComponent) getView();
1536
1537 if (lastPaintPosition == null ||
1538 lastPaintPosition.equals(getViewLocation())) {
1539 paintView(g);
1540 retValue = false;
1541 } else {
1542 // The image was scrolled. Manipulate the backing store and flush
1596 if (g instanceof Graphics2D) {
1597 Graphics2D g2d = (Graphics2D) g;
1598 oldComposite = g2d.getComposite();
1599 g2d.setComposite(AlphaComposite.Src);
1600 }
1601 rm.copyArea(this, g, blitFromX, blitFromY, blitW, blitH, bdx, bdy,
1602 false);
1603 if (oldComposite != null) {
1604 ((Graphics2D) g).setComposite(oldComposite);
1605 }
1606 // Paint the newly exposed region.
1607 int x = view.getX();
1608 int y = view.getY();
1609 g.translate(x, y);
1610 g.setClip(clipX, clipY, clipW, clipH);
1611 view.paintForceDoubleBuffered(g);
1612 g.translate(-x, -y);
1613 }
1614
1615 /**
1616 * Called to paint the view, usually when <code>blitPaint</code>
1617 * can not blit.
1618 *
1619 * @param g the <code>Graphics</code> context within which to paint
1620 */
1621 private void paintView(Graphics g) {
1622 Rectangle clip = g.getClipBounds();
1623 JComponent view = (JComponent)getView();
1624
1625 if (view.getWidth() >= getWidth()) {
1626 // Graphics is relative to JViewport, need to map to view's
1627 // coordinates space.
1628 int x = view.getX();
1629 int y = view.getY();
1630 g.translate(x, y);
1631 g.setClip(clip.x - x, clip.y - y, clip.width, clip.height);
1632 view.paintForceDoubleBuffered(g);
1633 g.translate(-x, -y);
1634 g.setClip(clip.x, clip.y, clip.width, clip.height);
1635 }
1636 else {
1637 // To avoid any problems that may result from the viewport being
1638 // bigger than the view we start painting from the viewport.
1639 try {
1640 inBlitPaint = true;
1641 paintForceDoubleBuffered(g);
1642 } finally {
1643 inBlitPaint = false;
1644 }
1645 }
1646 }
1647
1648 /**
1649 * Returns true if the viewport is not obscured by one of its ancestors,
1650 * or its ancestors children and if the viewport is showing. Blitting
1651 * when the view isn't showing will work,
1652 * or rather <code>copyArea</code> will work,
1653 * but will not produce the expected behavior.
1654 */
1655 private boolean canUseWindowBlitter() {
1656 if (!isShowing() || (!(getParent() instanceof JComponent) &&
1657 !(getView() instanceof JComponent))) {
1658 return false;
1659 }
1660 if (isPainting()) {
1661 // We're in the process of painting, don't blit. If we were
1662 // to blit we would draw on top of what we're already drawing,
1663 // so bail.
1664 return false;
1665 }
1666
1667 Rectangle dirtyRegion = RepaintManager.currentManager(this).
1668 getDirtyRegion((JComponent)getParent());
1669
1670 if (dirtyRegion != null && dirtyRegion.width > 0 &&
1671 dirtyRegion.height > 0) {
1672 // Part of the scrollpane needs to be repainted too, don't blit.
1728 ////////////////
1729
1730 /**
1731 * Gets the AccessibleContext associated with this JViewport.
1732 * For viewports, the AccessibleContext takes the form of an
1733 * AccessibleJViewport.
1734 * A new AccessibleJViewport instance is created if necessary.
1735 *
1736 * @return an AccessibleJViewport that serves as the
1737 * AccessibleContext of this JViewport
1738 */
1739 public AccessibleContext getAccessibleContext() {
1740 if (accessibleContext == null) {
1741 accessibleContext = new AccessibleJViewport();
1742 }
1743 return accessibleContext;
1744 }
1745
1746 /**
1747 * This class implements accessibility support for the
1748 * <code>JViewport</code> class. It provides an implementation of the
1749 * Java Accessibility API appropriate to viewport user-interface elements.
1750 * <p>
1751 * <strong>Warning:</strong>
1752 * Serialized objects of this class will not be compatible with
1753 * future Swing releases. The current serialization support is
1754 * appropriate for short term storage or RMI between applications running
1755 * the same version of Swing. As of 1.4, support for long term storage
1756 * of all JavaBeans™
1757 * has been added to the <code>java.beans</code> package.
1758 * Please see {@link java.beans.XMLEncoder}.
1759 */
1760 @SuppressWarnings("serial") // Same-version serialization only
1761 protected class AccessibleJViewport extends AccessibleJComponent {
1762 /**
1763 * Get the role of this object.
1764 *
1765 * @return an instance of AccessibleRole describing the role of
1766 * the object
1767 */
1768 public AccessibleRole getAccessibleRole() {
1769 return AccessibleRole.VIEWPORT;
1770 }
1771 } // inner class AccessibleJViewport
1772 }
|
29 import java.awt.event.*;
30 import java.awt.peer.ComponentPeer;
31 import java.beans.Transient;
32 import javax.swing.plaf.ViewportUI;
33
34 import javax.swing.event.*;
35 import javax.swing.border.*;
36 import javax.accessibility.*;
37
38 import java.io.Serializable;
39
40 import sun.awt.AWTAccessor;
41
42 /**
43 * The "viewport" or "porthole" through which you see the underlying
44 * information. When you scroll, what moves is the viewport. It is like
45 * peering through a camera's viewfinder. Moving the viewfinder upwards
46 * brings new things into view at the top of the picture and loses
47 * things that were at the bottom.
48 * <p>
49 * By default, {@code JViewport} is opaque. To change this, use the
50 * {@code setOpaque} method.
51 * <p>
52 * <b>NOTE:</b>We have implemented a faster scrolling algorithm that
53 * does not require a buffer to draw in. The algorithm works as follows:
54 * <ol><li>The view and parent view and checked to see if they are
55 * {@code JComponents},
56 * if they aren't, stop and repaint the whole viewport.
57 * <li>If the viewport is obscured by an ancestor, stop and repaint the whole
58 * viewport.
59 * <li>Compute the region that will become visible, if it is as big as
60 * the viewport, stop and repaint the whole view region.
61 * <li>Obtain the ancestor {@code Window}'s graphics and
62 * do a {@code copyArea} on the scrolled region.
63 * <li>Message the view to repaint the newly visible region.
64 * <li>The next time paint is invoked on the viewport, if the clip region
65 * is smaller than the viewport size a timer is kicked off to repaint the
66 * whole region.
67 * </ol>
68 * In general this approach is much faster. Compared to the backing store
69 * approach this avoids the overhead of maintaining an offscreen buffer and
70 * having to do two {@code copyArea}s.
71 * Compared to the non backing store case this
72 * approach will greatly reduce the painted region.
73 * <p>
74 * This approach can cause slower times than the backing store approach
75 * when the viewport is obscured by another window, or partially offscreen.
76 * When another window
77 * obscures the viewport the copyArea will copy garbage and a
78 * paint event will be generated by the system to inform us we need to
79 * paint the newly exposed region. The only way to handle this is to
80 * repaint the whole viewport, which can cause slower performance than the
81 * backing store case. In most applications very rarely will the user be
82 * scrolling while the viewport is obscured by another window or offscreen,
83 * so this optimization is usually worth the performance hit when obscured.
84 * <p>
85 * <strong>Warning:</strong> Swing is not thread safe. For more
86 * information see <a
87 * href="package-summary.html#threading">Swing's Threading
88 * Policy</a>.
89 * <p>
90 * <strong>Warning:</strong>
91 * Serialized objects of this class will not be compatible with
92 * future Swing releases. The current serialization support is
93 * appropriate for short term storage or RMI between applications running
94 * the same version of Swing. As of 1.4, support for long term storage
95 * of all JavaBeans™
96 * has been added to the {@code java.beans} package.
97 * Please see {@link java.beans.XMLEncoder}.
98 *
99 * @author Hans Muller
100 * @author Philip Milne
101 * @see JScrollPane
102 * @since 1.2
103 */
104 @SuppressWarnings("serial") // Same-version serialization only
105 public class JViewport extends JComponent implements Accessible
106 {
107 /**
108 * @see #getUIClassID
109 * @see #readObject
110 */
111 private static final String uiClassID = "ViewportUI";
112
113 /** Property used to indicate window blitting should not be done.
114 */
115 static final Object EnableWindowBlit = "EnableWindowBlit";
116
117 /**
118 * True when the viewport dimensions have been determined.
119 * The default is false.
120 */
121 protected boolean isViewSizeSet = false;
122
123 /**
124 * The last {@code viewPosition} that we've painted, so we know how
125 * much of the backing store image is valid.
126 */
127 protected Point lastPaintPosition = null;
128
129 /**
130 * True when this viewport is maintaining an offscreen image of its
131 * contents, so that some scrolling can take place using fast "bit-blit"
132 * operations instead of by accessing the view object to construct the
133 * display. The default is {@code false}.
134 *
135 * @deprecated As of Java 2 platform v1.3
136 * @see #setScrollMode
137 */
138 @Deprecated
139 protected boolean backingStore = false;
140
141 /** The view image used for a backing store. */
142 protected transient Image backingStoreImage = null;
143
144 /**
145 * The {@code scrollUnderway} flag is used for components like
146 * {@code JList}. When the downarrow key is pressed on a
147 * {@code JList} and the selected
148 * cell is the last in the list, the {@code scrollpane} autoscrolls.
149 * Here, the old selected cell needs repainting and so we need
150 * a flag to make the viewport do the optimized painting
151 * only when there is an explicit call to
152 * {@code setViewPosition(Point)}.
153 * When {@code setBounds} is called through other routes,
154 * the flag is off and the view repaints normally. Another approach
155 * would be to remove this from the {@code JViewport}
156 * class and have the {@code JList} manage this case by using
157 * {@code setBackingStoreEnabled}. The default is
158 * {@code false}.
159 */
160 protected boolean scrollUnderway = false;
161
162 /*
163 * Listener that is notified each time the view changes size.
164 */
165 private ComponentListener viewListener = null;
166
167 /* Only one {@code ChangeEvent} is needed per
168 * {@code JViewport} instance since the
169 * event's only (read-only) state is the source property. The source
170 * of events generated here is always "this".
171 */
172 private transient ChangeEvent changeEvent = null;
173
174 /**
175 * Use {@code graphics.copyArea} to implement scrolling.
176 * This is the fastest for most applications.
177 *
178 * @see #setScrollMode
179 * @since 1.3
180 */
181 public static final int BLIT_SCROLL_MODE = 1;
182
183 /**
184 * Draws viewport contents into an offscreen image.
185 * This was previously the default mode for {@code JTable}.
186 * This mode may offer advantages over "blit mode"
187 * in some cases, but it requires a large chunk of extra RAM.
188 *
189 * @see #setScrollMode
190 * @since 1.3
191 */
192 public static final int BACKINGSTORE_SCROLL_MODE = 2;
193
194 /**
195 * This mode uses the very simple method of redrawing the entire
196 * contents of the scrollpane each time it is scrolled.
197 * This was the default behavior in Swing 1.0 and Swing 1.1.
198 * Either of the other two options will provide better performance
199 * in most cases.
200 *
201 * @see #setScrollMode
202 * @since 1.3
203 */
204 public static final int SIMPLE_SCROLL_MODE = 0;
205
218 // not guaranteed to receive the paint event before other mouse events,
219 // so we can not be sure we haven't already copied garbage a bunch of
220 // times to different parts of the view. For that reason when a blit
221 // happens and the Component is obscured (the check for obscurity
222 // is not supported on all platforms and is checked via ComponentPeer
223 // methods) the ivar repaintAll is set to true. When paint is received
224 // if repaintAll is true (we previously did a blit) it is set to
225 // false, and if the clip region is smaller than the viewport
226 // waitingForRepaint is set to true and a timer is started. When
227 // the timer fires if waitingForRepaint is true, repaint is invoked.
228 // In the mean time, if the view is asked to scroll and waitingForRepaint
229 // is true, a blit will not happen, instead the non-backing store case
230 // of scrolling will happen, which will reset waitingForRepaint.
231 // waitingForRepaint is set to false in paint when the clip rect is
232 // bigger (or equal) to the size of the viewport.
233 // A Timer is used instead of just a repaint as it appeared to offer
234 // better performance.
235
236
237 /**
238 * This is set to true in {@code setViewPosition}
239 * if doing a window blit and the viewport is obscured.
240 */
241 private transient boolean repaintAll;
242
243 /**
244 * This is set to true in paint, if {@code repaintAll}
245 * is true and the clip rectangle does not match the bounds.
246 * If true, and scrolling happens the
247 * repaint manager is not cleared which then allows for the repaint
248 * previously invoked to succeed.
249 */
250 private transient boolean waitingForRepaint;
251
252 /**
253 * Instead of directly invoking repaint, a {@code Timer}
254 * is started and when it fires, repaint is invoked.
255 */
256 private transient Timer repaintTimer;
257
258 /**
259 * Set to true in paintView when paint is invoked.
260 */
261 private transient boolean inBlitPaint;
262
263 /**
264 * Whether or not a valid view has been installed.
265 */
266 private boolean hasHadValidView;
267
268 /**
269 * When view is changed we have to synchronize scrollbar values
270 * with viewport (see the BasicScrollPaneUI#syncScrollPaneWithViewport method).
271 * This flag allows to invoke that method while ScrollPaneLayout#layoutContainer
272 * is running.
273 */
274 private boolean viewChanged;
275
276 /** Creates a {@code JViewport}. */
277 public JViewport() {
278 super();
279 setLayout(createLayoutManager());
280 setOpaque(true);
281 updateUI();
282 setInheritsPopupMenu(true);
283 }
284
285
286
287 /**
288 * Returns the L&F object that renders this component.
289 *
290 * @return a {@code ViewportUI} object
291 * @since 1.3
292 */
293 public ViewportUI getUI() {
294 return (ViewportUI)ui;
295 }
296
297
298 /**
299 * Sets the L&F object that renders this component.
300 *
301 * @param ui the {@code ViewportUI} L&F object
302 * @see UIDefaults#getUI
303 * @beaninfo
304 * bound: true
305 * hidden: true
306 * attribute: visualUpdate true
307 * description: The UI object that implements the Component's LookAndFeel.
308 * @since 1.3
309 */
310 public void setUI(ViewportUI ui) {
311 super.setUI(ui);
312 }
313
314
315 /**
316 * Resets the UI property to a value from the current look and feel.
317 *
318 * @see JComponent#updateUI
319 */
320 public void updateUI() {
321 setUI((ViewportUI)UIManager.getUI(this));
322 }
323
324
325 /**
326 * Returns a string that specifies the name of the L&F class
327 * that renders this component.
328 *
329 * @return the string "ViewportUI"
330 *
331 * @see JComponent#getUIClassID
332 * @see UIDefaults#getUI
333 */
334 public String getUIClassID() {
335 return uiClassID;
336 }
337
338
339 /**
340 * Sets the {@code JViewport}'s one lightweight child,
341 * which can be {@code null}.
342 * (Since there is only one child which occupies the entire viewport,
343 * the {@code constraints} and {@code index}
344 * arguments are ignored.)
345 *
346 * @param child the lightweight {@code child} of the viewport
347 * @param constraints the {@code constraints} to be respected
348 * @param index the index
349 * @see #setView
350 */
351 protected void addImpl(Component child, Object constraints, int index) {
352 setView(child);
353 }
354
355
356 /**
357 * Removes the {@code Viewport}s one lightweight child.
358 *
359 * @see #setView
360 */
361 public void remove(Component child) {
362 child.removeComponentListener(viewListener);
363 super.remove(child);
364 }
365
366 /**
367 * Scrolls the view so that {@code Rectangle}
368 * within the view becomes visible.
369 * <p>
370 * This attempts to validate the view before scrolling if the
371 * view is currently not valid - {@code isValid} returns false.
372 * To avoid excessive validation when the containment hierarchy is
373 * being created this will not validate if one of the ancestors does not
374 * have a peer, or there is no validate root ancestor, or one of the
375 * ancestors is not a {@code Window} or {@code Applet}.
376 * <p>
377 * Note that this method will not scroll outside of the
378 * valid viewport; for example, if {@code contentRect} is larger
379 * than the viewport, scrolling will be confined to the viewport's
380 * bounds.
381 *
382 * @param contentRect the {@code Rectangle} to display
383 * @see JComponent#isValidateRoot
384 * @see java.awt.Component#isValid
385 */
386 public void scrollRectToVisible(Rectangle contentRect) {
387 Component view = getView();
388
389 if (view == null) {
390 return;
391 } else {
392 if (!view.isValid()) {
393 // If the view is not valid, validate. scrollRectToVisible
394 // may fail if the view is not valid first, contentRect
395 // could be bigger than invalid size.
396 validateView();
397 }
398 int dx, dy;
399
400 dx = positionAdjustment(getWidth(), contentRect.width, contentRect.x);
401 dy = positionAdjustment(getHeight(), contentRect.height, contentRect.y);
402
448 // true, the backing store is blitted. This fails
449 // if between the time setViewPosition is invoked
450 // and paint is received another repaint is queued
451 // indicating part of the view is invalid. There
452 // is no way for JViewport to notice another
453 // repaint has occurred and it ends up blitting
454 // what is now a dirty region and the repaint is
455 // never delivered.
456 // It just so happens JTable encounters this
457 // behavior by way of scrollRectToVisible, for
458 // this reason scrollUnderway is set to false
459 // here, which effectively disables the backing
460 // store.
461 scrollUnderway = false;
462 }
463 }
464 }
465 }
466
467 /**
468 * Ascends the {@code Viewport}'s parents stopping when
469 * a component is found that returns
470 * {@code true} to {@code isValidateRoot}.
471 * If all the {@code Component}'s parents are visible,
472 * {@code validate} will then be invoked on it. The
473 * {@code RepaintManager} is then invoked with
474 * {@code removeInvalidComponent}. This
475 * is the synchronous version of a {@code revalidate}.
476 */
477 private void validateView() {
478 Component validateRoot = SwingUtilities.getValidateRoot(this, false);
479
480 if (validateRoot == null) {
481 return;
482 }
483
484 // Validate the root.
485 validateRoot.validate();
486
487 // And let the RepaintManager it does not have to validate from
488 // validateRoot anymore.
489 RepaintManager rm = RepaintManager.currentManager(this);
490
491 if (rm != null) {
492 rm.removeInvalidComponent((JComponent)validateRoot);
493 }
494 }
495
532 // ---- | -> |---- |
533 // +-----+ +-----+
534 if (childAt <= 0 && childWidth <= parentWidth) {
535 return -childAt;
536 }
537
538 // +-----+ +-----+
539 //-------- | -> --------|
540 // +-----+ +-----+
541 if (childAt < 0 && childWidth >= parentWidth) {
542 return -childAt + parentWidth - childWidth;
543 }
544
545 return 0;
546 }
547
548
549 /**
550 * The viewport "scrolls" its child (called the "view") by the
551 * normal parent/child clipping (typically the view is moved in
552 * the opposite direction of the scroll). A non-{@code null} border,
553 * or non-zero insets, isn't supported, to prevent the geometry
554 * of this component from becoming complex enough to inhibit
555 * subclassing. To create a {@code JViewport} with a border,
556 * add it to a {@code JPanel} that has a border.
557 * <p>Note: If {@code border} is non-{@code null}, this
558 * method will throw an exception as borders are not supported on
559 * a {@code JViewPort}.
560 *
561 * @param border the {@code Border} to set
562 * @exception IllegalArgumentException this method is not implemented
563 */
564 public final void setBorder(Border border) {
565 if (border != null) {
566 throw new IllegalArgumentException("JViewport.setBorder() not supported");
567 }
568 }
569
570
571 /**
572 * Returns the insets (border) dimensions as (0,0,0,0), since borders
573 * are not supported on a {@code JViewport}.
574 *
575 * @return a {@code Rectangle} of zero dimension and zero origin
576 * @see #setBorder
577 */
578 public final Insets getInsets() {
579 return new Insets(0, 0, 0, 0);
580 }
581
582 /**
583 * Returns an {@code Insets} object containing this
584 * {@code JViewport}s inset values. The passed-in
585 * {@code Insets} object will be reinitialized, and
586 * all existing values within this object are overwritten.
587 *
588 * @param insets the {@code Insets} object which can be reused
589 * @return this viewports inset values
590 * @see #getInsets
591 * @beaninfo
592 * expert: true
593 */
594 public final Insets getInsets(Insets insets) {
595 insets.left = insets.top = insets.right = insets.bottom = 0;
596 return insets;
597 }
598
599
600 private Graphics getBackingStoreGraphics(Graphics g) {
601 Graphics bsg = backingStoreImage.getGraphics();
602 bsg.setColor(g.getColor());
603 bsg.setFont(g.getFont());
604 bsg.setClip(g.getClipBounds());
605 return bsg;
606 }
607
608
611 try {
612 super.paint(bsg);
613 g.drawImage(backingStoreImage, 0, 0, this);
614 } finally {
615 bsg.dispose();
616 }
617 }
618
619 private void paintViaBackingStore(Graphics g, Rectangle oClip) {
620 Graphics bsg = getBackingStoreGraphics(g);
621 try {
622 super.paint(bsg);
623 g.setClip(oClip);
624 g.drawImage(backingStoreImage, 0, 0, this);
625 } finally {
626 bsg.dispose();
627 }
628 }
629
630 /**
631 * The {@code JViewport} overrides the default implementation of
632 * this method (in {@code JComponent}) to return false.
633 * This ensures
634 * that the drawing machinery will call the {@code Viewport}'s
635 * {@code paint}
636 * implementation rather than messaging the {@code JViewport}'s
637 * children directly.
638 *
639 * @return false
640 */
641 public boolean isOptimizedDrawingEnabled() {
642 return false;
643 }
644
645 /**
646 * Returns true if scroll mode is a {@code BACKINGSTORE_SCROLL_MODE} to cause
647 * painting to originate from {@code JViewport}, or one of its
648 * ancestors. Otherwise returns {@code false}.
649 *
650 * @return true if scroll mode is a {@code BACKINGSTORE_SCROLL_MODE}.
651 * @see JComponent#isPaintingOrigin()
652 */
653 protected boolean isPaintingOrigin() {
654 return scrollMode == BACKINGSTORE_SCROLL_MODE;
655 }
656
657
658 /**
659 * Only used by the paint method below.
660 */
661 private Point getViewLocation() {
662 Component view = getView();
663 if (view != null) {
664 return view.getLocation();
665 }
666 else {
667 return new Point(0,0);
668 }
669 }
670
671 /**
672 * Depending on whether the {@code backingStore} is enabled,
673 * either paint the image through the backing store or paint
674 * just the recently exposed part, using the backing store
675 * to "blit" the remainder.
676 * <blockquote>
677 * The term "blit" is the pronounced version of the PDP-10
678 * BLT (BLock Transfer) instruction, which copied a block of
679 * bits. (In case you were curious.)
680 * </blockquote>
681 *
682 * @param g the {@code Graphics} context within which to paint
683 */
684 public void paint(Graphics g)
685 {
686 int width = getWidth();
687 int height = getHeight();
688
689 if ((width <= 0) || (height <= 0)) {
690 return;
691 }
692
693 if (inBlitPaint) {
694 // We invoked paint as part of copyArea cleanup, let it through.
695 super.paint(g);
696 return;
697 }
698
699 if (repaintAll) {
700 repaintAll = false;
701 Rectangle clipB = g.getClipBounds();
702 if (clipB.width < getWidth() ||
804 // Paint the rest of the view; the part that has just been exposed.
805 Rectangle r = viewBounds.intersection(blitPaint);
806 bsg.setClip(r);
807 super.paint(bsg);
808
809 // Copy whole of the backing store to g.
810 g.drawImage(backingStoreImage, 0, 0, this);
811 } finally {
812 bsg.dispose();
813 }
814 }
815 }
816 }
817 lastPaintPosition = getViewLocation();
818 scrollUnderway = false;
819 }
820
821
822 /**
823 * Sets the bounds of this viewport. If the viewport's width
824 * or height has changed, fire a {@code StateChanged} event.
825 *
826 * @param x left edge of the origin
827 * @param y top edge of the origin
828 * @param w width in pixels
829 * @param h height in pixels
830 *
831 * @see JComponent#reshape(int, int, int, int)
832 */
833 @SuppressWarnings("deprecation")
834 public void reshape(int x, int y, int w, int h) {
835 boolean sizeChanged = (getWidth() != w) || (getHeight() != h);
836 if (sizeChanged) {
837 backingStoreImage = null;
838 }
839 super.reshape(x, y, w, h);
840 if (sizeChanged || viewChanged) {
841 viewChanged = false;
842
843 fireStateChanged();
844 }
861 * @see #BACKINGSTORE_SCROLL_MODE
862 * @see #SIMPLE_SCROLL_MODE
863 *
864 * @beaninfo
865 * bound: false
866 * description: Method of moving contents for incremental scrolls.
867 * enum: BLIT_SCROLL_MODE JViewport.BLIT_SCROLL_MODE
868 * BACKINGSTORE_SCROLL_MODE JViewport.BACKINGSTORE_SCROLL_MODE
869 * SIMPLE_SCROLL_MODE JViewport.SIMPLE_SCROLL_MODE
870 *
871 * @since 1.3
872 */
873 public void setScrollMode(int mode) {
874 scrollMode = mode;
875 backingStore = mode == BACKINGSTORE_SCROLL_MODE;
876 }
877
878 /**
879 * Returns the current scrolling mode.
880 *
881 * @return the {@code scrollMode} property
882 * @see #setScrollMode
883 * @since 1.3
884 */
885 public int getScrollMode() {
886 return scrollMode;
887 }
888
889 /**
890 * Returns {@code true} if this viewport is maintaining
891 * an offscreen image of its contents.
892 *
893 * @return {@code true} if {@code scrollMode} is
894 * {@code BACKINGSTORE_SCROLL_MODE}
895 *
896 * @deprecated As of Java 2 platform v1.3, replaced by
897 * {@code getScrollMode()}.
898 */
899 @Deprecated
900 public boolean isBackingStoreEnabled() {
901 return scrollMode == BACKINGSTORE_SCROLL_MODE;
902 }
903
904
905 /**
906 * If true if this viewport will maintain an offscreen
907 * image of its contents. The image is used to reduce the cost
908 * of small one dimensional changes to the {@code viewPosition}.
909 * Rather than repainting the entire viewport we use
910 * {@code Graphics.copyArea} to effect some of the scroll.
911 *
912 * @param enabled if true, maintain an offscreen backing store
913 *
914 * @deprecated As of Java 2 platform v1.3, replaced by
915 * {@code setScrollMode()}.
916 */
917 @Deprecated
918 public void setBackingStoreEnabled(boolean enabled) {
919 if (enabled) {
920 setScrollMode(BACKINGSTORE_SCROLL_MODE);
921 } else {
922 setScrollMode(BLIT_SCROLL_MODE);
923 }
924 }
925
926 private boolean isBlitting() {
927 Component view = getView();
928 return (scrollMode == BLIT_SCROLL_MODE) &&
929 (view instanceof JComponent) && view.isOpaque();
930 }
931
932
933 /**
934 * Returns the {@code JViewport}'s one child or {@code null}.
935 *
936 * @return the viewports child, or {@code null} if none exists
937 *
938 * @see #setView
939 */
940 public Component getView() {
941 return (getComponentCount() > 0) ? getComponent(0) : null;
942 }
943
944 /**
945 * Sets the {@code JViewport}'s one lightweight child
946 * ({@code view}), which can be {@code null}.
947 *
948 * @param view the viewport's new lightweight child
949 *
950 * @see #getView
951 */
952 public void setView(Component view) {
953
954 /* Remove the viewport's existing children, if any.
955 * Note that removeAll() isn't used here because it
956 * doesn't call remove() (which JViewport overrides).
957 */
958 int n = getComponentCount();
959 for(int i = n - 1; i >= 0; i--) {
960 remove(getComponent(i));
961 }
962
963 isViewSizeSet = false;
964
965 if (view != null) {
966 super.addImpl(view, null, -1);
971 if (hasHadValidView) {
972 // Only fire a change if a view has been installed.
973 fireStateChanged();
974 }
975 else if (view != null) {
976 hasHadValidView = true;
977 }
978
979 viewChanged = true;
980
981 revalidate();
982 repaint();
983 }
984
985
986 /**
987 * If the view's size hasn't been explicitly set, return the
988 * preferred size, otherwise return the view's current size.
989 * If there is no view, return 0,0.
990 *
991 * @return a {@code Dimension} object specifying the size of the view
992 */
993 public Dimension getViewSize() {
994 Component view = getView();
995
996 if (view == null) {
997 return new Dimension(0,0);
998 }
999 else if (isViewSizeSet) {
1000 return view.getSize();
1001 }
1002 else {
1003 return view.getPreferredSize();
1004 }
1005 }
1006
1007
1008 /**
1009 * Sets the size of the view. A state changed event will be fired.
1010 *
1011 * @param newSize a {@code Dimension} object specifying the new
1012 * size of the view
1013 */
1014 public void setViewSize(Dimension newSize) {
1015 Component view = getView();
1016 if (view != null) {
1017 Dimension oldSize = view.getSize();
1018 if (!newSize.equals(oldSize)) {
1019 // scrollUnderway will be true if this is invoked as the
1020 // result of a validate and setViewPosition was previously
1021 // invoked.
1022 scrollUnderway = false;
1023 view.setSize(newSize);
1024 isViewSizeSet = true;
1025 fireStateChanged();
1026 }
1027 }
1028 }
1029
1030 /**
1031 * Returns the view coordinates that appear in the upper left
1032 * hand corner of the viewport, or 0,0 if there's no view.
1033 *
1034 * @return a {@code Point} object giving the upper left coordinates
1035 */
1036 public Point getViewPosition() {
1037 Component view = getView();
1038 if (view != null) {
1039 Point p = view.getLocation();
1040 p.x = -p.x;
1041 p.y = -p.y;
1042 return p;
1043 }
1044 else {
1045 return new Point(0,0);
1046 }
1047 }
1048
1049
1050 /**
1051 * Sets the view coordinates that appear in the upper left
1052 * hand corner of the viewport, does nothing if there's no view.
1053 *
1054 * @param p a {@code Point} object giving the upper left coordinates
1055 */
1056 public void setViewPosition(Point p)
1057 {
1058 Component view = getView();
1059 if (view == null) {
1060 return;
1061 }
1062
1063 int oldX, oldY, x = p.x, y = p.y;
1064
1065 /* Collect the old x,y values for the views location
1066 * and do the song and dance to avoid allocating
1067 * a Rectangle object if we don't have to.
1068 */
1069 if (view instanceof JComponent) {
1070 JComponent c = (JComponent)view;
1071 oldX = c.getX();
1072 oldY = c.getY();
1073 }
1074 else {
1115 else {
1116 // The visible region is dirty, no point in doing copyArea
1117 view.setLocation(newX, newY);
1118 repaintAll = false;
1119 }
1120 }
1121 else {
1122 scrollUnderway = true;
1123 // This calls setBounds(), and then repaint().
1124 view.setLocation(newX, newY);
1125 repaintAll = false;
1126 }
1127 // we must validate the hierarchy to not break the hw/lw mixing
1128 revalidate();
1129 fireStateChanged();
1130 }
1131 }
1132
1133
1134 /**
1135 * Returns a rectangle whose origin is {@code getViewPosition}
1136 * and size is {@code getExtentSize}.
1137 * This is the visible part of the view, in view coordinates.
1138 *
1139 * @return a {@code Rectangle} giving the visible part of
1140 * the view using view coordinates.
1141 */
1142 public Rectangle getViewRect() {
1143 return new Rectangle(getViewPosition(), getExtentSize());
1144 }
1145
1146
1147 /**
1148 * Computes the parameters for a blit where the backing store image
1149 * currently contains {@code oldLoc} in the upper left hand corner
1150 * and we're scrolling to {@code newLoc}.
1151 * The parameters are modified
1152 * to return the values required for the blit.
1153 *
1154 * @param dx the horizontal delta
1155 * @param dy the vertical delta
1156 * @param blitFrom the {@code Point} we're blitting from
1157 * @param blitTo the {@code Point} we're blitting to
1158 * @param blitSize the {@code Dimension} of the area to blit
1159 * @param blitPaint the area to blit
1160 * @return true if the parameters are modified and we're ready to blit;
1161 * false otherwise
1162 */
1163 protected boolean computeBlit(
1164 int dx,
1165 int dy,
1166 Point blitFrom,
1167 Point blitTo,
1168 Dimension blitSize,
1169 Rectangle blitPaint)
1170 {
1171 int dxAbs = Math.abs(dx);
1172 int dyAbs = Math.abs(dy);
1173 Dimension extentSize = getExtentSize();
1174
1175 if ((dx == 0) && (dy != 0) && (dyAbs < extentSize.height)) {
1176 if (dy < 0) {
1177 blitFrom.y = -dy;
1178 blitTo.y = 0;
1210 blitPaint.y = blitFrom.y = blitTo.y = 0;
1211
1212 blitSize.width = extentSize.width - dxAbs;
1213 blitSize.height = extentSize.height;
1214
1215 blitPaint.width = dxAbs;
1216 blitPaint.height = extentSize.height;
1217
1218 return true;
1219 }
1220
1221 else {
1222 return false;
1223 }
1224 }
1225
1226
1227 /**
1228 * Returns the size of the visible part of the view in view coordinates.
1229 *
1230 * @return a {@code Dimension} object giving the size of the view
1231 */
1232 @Transient
1233 public Dimension getExtentSize() {
1234 return getSize();
1235 }
1236
1237
1238 /**
1239 * Converts a size in pixel coordinates to view coordinates.
1240 * Subclasses of viewport that support "logical coordinates"
1241 * will override this method.
1242 *
1243 * @param size a {@code Dimension} object using pixel coordinates
1244 * @return a {@code Dimension} object converted to view coordinates
1245 */
1246 public Dimension toViewCoordinates(Dimension size) {
1247 return new Dimension(size);
1248 }
1249
1250 /**
1251 * Converts a point in pixel coordinates to view coordinates.
1252 * Subclasses of viewport that support "logical coordinates"
1253 * will override this method.
1254 *
1255 * @param p a {@code Point} object using pixel coordinates
1256 * @return a {@code Point} object converted to view coordinates
1257 */
1258 public Point toViewCoordinates(Point p) {
1259 return new Point(p);
1260 }
1261
1262
1263 /**
1264 * Sets the size of the visible part of the view using view coordinates.
1265 *
1266 * @param newExtent a {@code Dimension} object specifying
1267 * the size of the view
1268 */
1269 public void setExtentSize(Dimension newExtent) {
1270 Dimension oldExtent = getExtentSize();
1271 if (!newExtent.equals(oldExtent)) {
1272 setSize(newExtent);
1273 fireStateChanged();
1274 }
1275 }
1276
1277 /**
1278 * A listener for the view.
1279 * <p>
1280 * <strong>Warning:</strong>
1281 * Serialized objects of this class will not be compatible with
1282 * future Swing releases. The current serialization support is
1283 * appropriate for short term storage or RMI between applications running
1284 * the same version of Swing. As of 1.4, support for long term storage
1285 * of all JavaBeans™
1286 * has been added to the {@code java.beans} package.
1287 * Please see {@link java.beans.XMLEncoder}.
1288 */
1289 @SuppressWarnings("serial") // Same-version serialization only
1290 protected class ViewListener extends ComponentAdapter implements Serializable
1291 {
1292 public void componentResized(ComponentEvent e) {
1293 fireStateChanged();
1294 revalidate();
1295 }
1296 }
1297
1298 /**
1299 * Creates a listener for the view.
1300 * @return a {@code ViewListener}
1301 */
1302 protected ViewListener createViewListener() {
1303 return new ViewListener();
1304 }
1305
1306
1307 /**
1308 * Subclassers can override this to install a different
1309 * layout manager (or {@code null}) in the constructor. Returns
1310 * the {@code LayoutManager} to install on the {@code JViewport}.
1311 *
1312 * @return a {@code LayoutManager}
1313 */
1314 protected LayoutManager createLayoutManager() {
1315 return ViewportLayout.SHARED_INSTANCE;
1316 }
1317
1318
1319 /**
1320 * Adds a {@code ChangeListener} to the list that is
1321 * notified each time the view's
1322 * size, position, or the viewport's extent size has changed.
1323 *
1324 * @param l the {@code ChangeListener} to add
1325 * @see #removeChangeListener
1326 * @see #setViewPosition
1327 * @see #setViewSize
1328 * @see #setExtentSize
1329 */
1330 public void addChangeListener(ChangeListener l) {
1331 listenerList.add(ChangeListener.class, l);
1332 }
1333
1334 /**
1335 * Removes a {@code ChangeListener} from the list that's notified each
1336 * time the views size, position, or the viewports extent size
1337 * has changed.
1338 *
1339 * @param l the {@code ChangeListener} to remove
1340 * @see #addChangeListener
1341 */
1342 public void removeChangeListener(ChangeListener l) {
1343 listenerList.remove(ChangeListener.class, l);
1344 }
1345
1346 /**
1347 * Returns an array of all the {@code ChangeListener}s added
1348 * to this JViewport with addChangeListener().
1349 *
1350 * @return all of the {@code ChangeListener}s added or an empty
1351 * array if no listeners have been added
1352 * @since 1.4
1353 */
1354 public ChangeListener[] getChangeListeners() {
1355 return listenerList.getListeners(ChangeListener.class);
1356 }
1357
1358 /**
1359 * Notifies all {@code ChangeListeners} when the views
1360 * size, position, or the viewports extent size has changed.
1361 *
1362 * @see #addChangeListener
1363 * @see #removeChangeListener
1364 * @see EventListenerList
1365 */
1366 protected void fireStateChanged()
1367 {
1368 Object[] listeners = listenerList.getListenerList();
1369 for (int i = listeners.length - 2; i >= 0; i -= 2) {
1370 if (listeners[i] == ChangeListener.class) {
1371 if (changeEvent == null) {
1372 changeEvent = new ChangeEvent(this);
1373 }
1374 ((ChangeListener)listeners[i + 1]).stateChanged(changeEvent);
1375 }
1376 }
1377 }
1378
1379 /**
1380 * Always repaint in the parents coordinate system to make sure
1381 * only one paint is performed by the {@code RepaintManager}.
1382 *
1383 * @param tm maximum time in milliseconds before update
1384 * @param x the {@code x} coordinate (pixels over from left)
1385 * @param y the {@code y} coordinate (pixels down from top)
1386 * @param w the width
1387 * @param h the height
1388 * @see java.awt.Component#update(java.awt.Graphics)
1389 */
1390 public void repaint(long tm, int x, int y, int w, int h) {
1391 Container parent = getParent();
1392 if(parent != null)
1393 parent.repaint(tm,x+getX(),y+getY(),w,h);
1394 else
1395 super.repaint(tm,x,y,w,h);
1396 }
1397
1398
1399 /**
1400 * Returns a string representation of this {@code JViewport}.
1401 * This method
1402 * is intended to be used only for debugging purposes, and the
1403 * content and format of the returned string may vary between
1404 * implementations. The returned string may be empty but may not
1405 * be {@code null}.
1406 *
1407 * @return a string representation of this {@code JViewport}
1408 */
1409 protected String paramString() {
1410 String isViewSizeSetString = (isViewSizeSet ?
1411 "true" : "false");
1412 String lastPaintPositionString = (lastPaintPosition != null ?
1413 lastPaintPosition.toString() : "");
1414 String scrollUnderwayString = (scrollUnderway ?
1415 "true" : "false");
1416
1417 return super.paramString() +
1418 ",isViewSizeSet=" + isViewSizeSetString +
1419 ",lastPaintPosition=" + lastPaintPositionString +
1420 ",scrollUnderway=" + scrollUnderwayString;
1421 }
1422
1423 //
1424 // Following is used when doBlit is true.
1425 //
1426
1427 /**
1428 * Notifies listeners of a property change. This is subclassed to update
1429 * the {@code windowBlit} property.
1430 * (The {@code putClientProperty} property is final).
1431 *
1432 * @param propertyName a string containing the property name
1433 * @param oldValue the old value of the property
1434 * @param newValue the new value of the property
1435 */
1436 protected void firePropertyChange(String propertyName, Object oldValue,
1437 Object newValue) {
1438 super.firePropertyChange(propertyName, oldValue, newValue);
1439 if (propertyName.equals(EnableWindowBlit)) {
1440 if (newValue != null) {
1441 setScrollMode(BLIT_SCROLL_MODE);
1442 } else {
1443 setScrollMode(SIMPLE_SCROLL_MODE);
1444 }
1445 }
1446 }
1447
1448 /**
1449 * Returns true if the component needs to be completely repainted after
1450 * a blit and a paint is received.
1478
1479 private Timer createRepaintTimer() {
1480 Timer timer = new Timer(300, new ActionListener() {
1481 public void actionPerformed(ActionEvent ae) {
1482 // waitingForRepaint will be false if a paint came down
1483 // with the complete clip rect, in which case we don't
1484 // have to cause a repaint.
1485 if (waitingForRepaint) {
1486 repaint();
1487 }
1488 }
1489 });
1490 timer.setRepeats(false);
1491 return timer;
1492 }
1493
1494 /**
1495 * If the repaint manager has a dirty region for the view, the view is
1496 * asked to paint.
1497 *
1498 * @param g the {@code Graphics} context within which to paint
1499 */
1500 private void flushViewDirtyRegion(Graphics g, Rectangle dirty) {
1501 JComponent view = (JComponent) getView();
1502 if(dirty != null && dirty.width > 0 && dirty.height > 0) {
1503 dirty.x += view.getX();
1504 dirty.y += view.getY();
1505 Rectangle clip = g.getClipBounds();
1506 if (clip == null) {
1507 // Only happens in 1.2
1508 g.setClip(0, 0, getWidth(), getHeight());
1509 }
1510 g.clipRect(dirty.x, dirty.y, dirty.width, dirty.height);
1511 clip = g.getClipBounds();
1512 // Only paint the dirty region if it is visible.
1513 if (clip.width > 0 && clip.height > 0) {
1514 paintView(g);
1515 }
1516 }
1517 }
1518
1519 /**
1520 * Used when blitting.
1521 *
1522 * @param g the {@code Graphics} context within which to paint
1523 * @return true if blitting succeeded; otherwise false
1524 */
1525 private boolean windowBlitPaint(Graphics g) {
1526 int width = getWidth();
1527 int height = getHeight();
1528
1529 if ((width == 0) || (height == 0)) {
1530 return false;
1531 }
1532
1533 boolean retValue;
1534 RepaintManager rm = RepaintManager.currentManager(this);
1535 JComponent view = (JComponent) getView();
1536
1537 if (lastPaintPosition == null ||
1538 lastPaintPosition.equals(getViewLocation())) {
1539 paintView(g);
1540 retValue = false;
1541 } else {
1542 // The image was scrolled. Manipulate the backing store and flush
1596 if (g instanceof Graphics2D) {
1597 Graphics2D g2d = (Graphics2D) g;
1598 oldComposite = g2d.getComposite();
1599 g2d.setComposite(AlphaComposite.Src);
1600 }
1601 rm.copyArea(this, g, blitFromX, blitFromY, blitW, blitH, bdx, bdy,
1602 false);
1603 if (oldComposite != null) {
1604 ((Graphics2D) g).setComposite(oldComposite);
1605 }
1606 // Paint the newly exposed region.
1607 int x = view.getX();
1608 int y = view.getY();
1609 g.translate(x, y);
1610 g.setClip(clipX, clipY, clipW, clipH);
1611 view.paintForceDoubleBuffered(g);
1612 g.translate(-x, -y);
1613 }
1614
1615 /**
1616 * Called to paint the view, usually when {@code blitPaint}
1617 * can not blit.
1618 *
1619 * @param g the {@code Graphics} context within which to paint
1620 */
1621 private void paintView(Graphics g) {
1622 Rectangle clip = g.getClipBounds();
1623 JComponent view = (JComponent)getView();
1624
1625 if (view.getWidth() >= getWidth()) {
1626 // Graphics is relative to JViewport, need to map to view's
1627 // coordinates space.
1628 int x = view.getX();
1629 int y = view.getY();
1630 g.translate(x, y);
1631 g.setClip(clip.x - x, clip.y - y, clip.width, clip.height);
1632 view.paintForceDoubleBuffered(g);
1633 g.translate(-x, -y);
1634 g.setClip(clip.x, clip.y, clip.width, clip.height);
1635 }
1636 else {
1637 // To avoid any problems that may result from the viewport being
1638 // bigger than the view we start painting from the viewport.
1639 try {
1640 inBlitPaint = true;
1641 paintForceDoubleBuffered(g);
1642 } finally {
1643 inBlitPaint = false;
1644 }
1645 }
1646 }
1647
1648 /**
1649 * Returns true if the viewport is not obscured by one of its ancestors,
1650 * or its ancestors children and if the viewport is showing. Blitting
1651 * when the view isn't showing will work,
1652 * or rather {@code copyArea} will work,
1653 * but will not produce the expected behavior.
1654 */
1655 private boolean canUseWindowBlitter() {
1656 if (!isShowing() || (!(getParent() instanceof JComponent) &&
1657 !(getView() instanceof JComponent))) {
1658 return false;
1659 }
1660 if (isPainting()) {
1661 // We're in the process of painting, don't blit. If we were
1662 // to blit we would draw on top of what we're already drawing,
1663 // so bail.
1664 return false;
1665 }
1666
1667 Rectangle dirtyRegion = RepaintManager.currentManager(this).
1668 getDirtyRegion((JComponent)getParent());
1669
1670 if (dirtyRegion != null && dirtyRegion.width > 0 &&
1671 dirtyRegion.height > 0) {
1672 // Part of the scrollpane needs to be repainted too, don't blit.
1728 ////////////////
1729
1730 /**
1731 * Gets the AccessibleContext associated with this JViewport.
1732 * For viewports, the AccessibleContext takes the form of an
1733 * AccessibleJViewport.
1734 * A new AccessibleJViewport instance is created if necessary.
1735 *
1736 * @return an AccessibleJViewport that serves as the
1737 * AccessibleContext of this JViewport
1738 */
1739 public AccessibleContext getAccessibleContext() {
1740 if (accessibleContext == null) {
1741 accessibleContext = new AccessibleJViewport();
1742 }
1743 return accessibleContext;
1744 }
1745
1746 /**
1747 * This class implements accessibility support for the
1748 * {@code JViewport} class. It provides an implementation of the
1749 * Java Accessibility API appropriate to viewport user-interface elements.
1750 * <p>
1751 * <strong>Warning:</strong>
1752 * Serialized objects of this class will not be compatible with
1753 * future Swing releases. The current serialization support is
1754 * appropriate for short term storage or RMI between applications running
1755 * the same version of Swing. As of 1.4, support for long term storage
1756 * of all JavaBeans™
1757 * has been added to the {@code java.beans} package.
1758 * Please see {@link java.beans.XMLEncoder}.
1759 */
1760 @SuppressWarnings("serial") // Same-version serialization only
1761 protected class AccessibleJViewport extends AccessibleJComponent {
1762 /**
1763 * Get the role of this object.
1764 *
1765 * @return an instance of AccessibleRole describing the role of
1766 * the object
1767 */
1768 public AccessibleRole getAccessibleRole() {
1769 return AccessibleRole.VIEWPORT;
1770 }
1771 } // inner class AccessibleJViewport
1772 }
|