41 |
import java.awt.Component; |
import java.awt.Component; |
42 |
import java.awt.Dimension; |
import java.awt.Dimension; |
43 |
import java.awt.Graphics; |
import java.awt.Graphics; |
44 |
|
import java.awt.Image; |
45 |
import java.awt.Insets; |
import java.awt.Insets; |
46 |
import java.awt.LayoutManager; |
import java.awt.LayoutManager; |
47 |
import java.awt.Point; |
import java.awt.Point; |
182 |
} |
} |
183 |
} |
} |
184 |
|
|
|
private static final long serialVersionUID = -6925142919680527970L; |
|
|
|
|
185 |
public static final int SIMPLE_SCROLL_MODE = 0; |
public static final int SIMPLE_SCROLL_MODE = 0; |
186 |
public static final int BLIT_SCROLL_MODE = 1; |
public static final int BLIT_SCROLL_MODE = 1; |
187 |
public static final int BACKINGSTORE_SCROLL_MODE = 2; |
public static final int BACKINGSTORE_SCROLL_MODE = 2; |
188 |
|
|
189 |
|
private static final long serialVersionUID = -6925142919680527970L; |
190 |
|
|
191 |
|
protected boolean scrollUnderway; |
192 |
|
protected boolean isViewSizeSet; |
193 |
|
|
194 |
|
/** |
195 |
|
* This flag indicates whether we use a backing store for drawing. |
196 |
|
* |
197 |
|
* @deprecated since JDK 1.3 |
198 |
|
*/ |
199 |
|
protected boolean backingStore; |
200 |
|
|
201 |
|
/** |
202 |
|
* The backingstore image used for the backingstore and blit scroll methods. |
203 |
|
*/ |
204 |
|
protected Image backingStoreImage; |
205 |
|
|
206 |
|
/** |
207 |
|
* The position at which the view has been drawn the last time. This is used |
208 |
|
* to determine the bittable area. |
209 |
|
*/ |
210 |
|
protected Point lastPaintPosition; |
211 |
|
|
212 |
ChangeEvent changeEvent = new ChangeEvent(this); |
ChangeEvent changeEvent = new ChangeEvent(this); |
213 |
|
|
214 |
int scrollMode; |
int scrollMode; |
215 |
|
|
|
protected boolean scrollUnderway; |
|
|
protected boolean isViewSizeSet; |
|
|
|
|
216 |
/** |
/** |
217 |
* The width and height of the Viewport's area in terms of view |
* The width and height of the Viewport's area in terms of view |
218 |
* coordinates. Typically this will be the same as the width and height |
* coordinates. Typically this will be the same as the width and height |
220 |
* width and height, which it may do, for example if it magnifies or |
* width and height, which it may do, for example if it magnifies or |
221 |
* rotates its view. |
* rotates its view. |
222 |
* |
* |
223 |
* @see #toViewCoordinates |
* @see #toViewCoordinates(Dimension) |
224 |
*/ |
*/ |
225 |
Dimension extentSize; |
Dimension extentSize; |
226 |
|
|
227 |
/** |
/** |
228 |
* The width and height of the view in its own coordinate space. |
* The width and height of the view in its own coordinate space. |
229 |
*/ |
*/ |
|
|
|
230 |
Dimension viewSize; |
Dimension viewSize; |
231 |
|
|
|
Point lastPaintPosition; |
|
|
|
|
232 |
/** |
/** |
233 |
* The ViewListener instance. |
* The ViewListener instance. |
234 |
*/ |
*/ |
235 |
ViewListener viewListener; |
ViewListener viewListener; |
236 |
|
|
237 |
/** |
/** |
238 |
* The accessible context of this <code>JViewport</code>. |
* Stores the location from where to blit. This is a cached Point object used |
239 |
|
* in blitting calculations. |
240 |
*/ |
*/ |
241 |
AccessibleContext accessibleContext; |
Point cachedBlitFrom; |
242 |
|
|
243 |
|
/** |
244 |
|
* Stores the location where to blit to. This is a cached Point object used |
245 |
|
* in blitting calculations. |
246 |
|
*/ |
247 |
|
Point cachedBlitTo; |
248 |
|
|
249 |
|
/** |
250 |
|
* Stores the width of the blitted area. This is a cached Dimension object |
251 |
|
* used in blitting calculations. |
252 |
|
*/ |
253 |
|
Dimension cachedBlitSize; |
254 |
|
|
255 |
|
/** |
256 |
|
* Stores the bounds of the area that needs to be repainted. This is a cached |
257 |
|
* Rectangle object used in blitting calculations. |
258 |
|
*/ |
259 |
|
Rectangle cachedBlitPaint; |
260 |
|
|
261 |
|
boolean damaged = true; |
262 |
|
|
263 |
public JViewport() |
public JViewport() |
264 |
{ |
{ |
266 |
setScrollMode(BLIT_SCROLL_MODE); |
setScrollMode(BLIT_SCROLL_MODE); |
267 |
setLayout(createLayoutManager()); |
setLayout(createLayoutManager()); |
268 |
updateUI(); |
updateUI(); |
269 |
|
lastPaintPosition = new Point(); |
270 |
|
cachedBlitFrom = new Point(); |
271 |
|
cachedBlitTo = new Point(); |
272 |
|
cachedBlitSize = new Dimension(); |
273 |
|
cachedBlitPaint = new Rectangle(); |
274 |
} |
} |
275 |
|
|
276 |
public Dimension getExtentSize() |
public Dimension getExtentSize() |
364 |
isViewSizeSet = false; |
isViewSizeSet = false; |
365 |
fireStateChanged(); |
fireStateChanged(); |
366 |
} |
} |
367 |
|
repaint(); |
368 |
} |
} |
369 |
|
|
370 |
public Rectangle getViewRect() |
public Rectangle getViewRect() |
433 |
|
|
434 |
public void revalidate() |
public void revalidate() |
435 |
{ |
{ |
436 |
|
damaged = true; |
437 |
fireStateChanged(); |
fireStateChanged(); |
438 |
super.revalidate(); |
super.revalidate(); |
439 |
} |
} |
440 |
|
|
441 |
public void reshape(int x, int y, int w, int h) |
public void reshape(int x, int y, int w, int h) |
442 |
{ |
{ |
443 |
|
damaged = true; |
444 |
boolean changed = |
boolean changed = |
445 |
(x != getX()) |
(x != getX()) |
446 |
|| (y != getY()) |
|| (y != getY()) |
451 |
fireStateChanged(); |
fireStateChanged(); |
452 |
} |
} |
453 |
|
|
|
protected void addImpl(Component comp, Object constraints, int index) |
|
|
{ |
|
|
if (getComponentCount() > 0) |
|
|
remove(getComponents()[0]); |
|
|
|
|
|
super.addImpl(comp, constraints, index); |
|
|
} |
|
|
|
|
454 |
public final Insets getInsets() |
public final Insets getInsets() |
455 |
{ |
{ |
456 |
return new Insets(0,0,0,0); |
return new Insets(0,0,0,0); |
474 |
|
|
475 |
public void paint(Graphics g) |
public void paint(Graphics g) |
476 |
{ |
{ |
477 |
paintComponent(g); |
Component view = getView(); |
478 |
|
|
479 |
|
if (view == null) |
480 |
|
return; |
481 |
|
|
482 |
|
Point pos = getViewPosition(); |
483 |
|
Rectangle viewBounds = view.getBounds(); |
484 |
|
Rectangle portBounds = getBounds(); |
485 |
|
|
486 |
|
if (viewBounds.width == 0 |
487 |
|
|| viewBounds.height == 0 |
488 |
|
|| portBounds.width == 0 |
489 |
|
|| portBounds.height == 0) |
490 |
|
return; |
491 |
|
|
492 |
|
switch (getScrollMode()) |
493 |
|
{ |
494 |
|
|
495 |
|
case JViewport.BACKINGSTORE_SCROLL_MODE: |
496 |
|
paintBackingStore(g); |
497 |
|
break; |
498 |
|
case JViewport.BLIT_SCROLL_MODE: |
499 |
|
paintBlit(g); |
500 |
|
break; |
501 |
|
case JViewport.SIMPLE_SCROLL_MODE: |
502 |
|
default: |
503 |
|
paintSimple(g); |
504 |
|
break; |
505 |
|
} |
506 |
|
damaged = false; |
507 |
} |
} |
508 |
|
|
509 |
public void addChangeListener(ChangeListener listener) |
public void addChangeListener(ChangeListener listener) |
521 |
return (ChangeListener[]) getListeners(ChangeListener.class); |
return (ChangeListener[]) getListeners(ChangeListener.class); |
522 |
} |
} |
523 |
|
|
|
protected void fireStateChanged() |
|
|
{ |
|
|
ChangeListener[] listeners = getChangeListeners(); |
|
|
for (int i = 0; i < listeners.length; ++i) |
|
|
listeners[i].stateChanged(changeEvent); |
|
|
} |
|
|
|
|
524 |
/** |
/** |
525 |
* This method returns the String ID of the UI class of Separator. |
* This method returns the String ID of the UI class of Separator. |
526 |
* |
* |
566 |
} |
} |
567 |
|
|
568 |
/** |
/** |
|
* Creates a {@link ViewListener} that is supposed to listen for |
|
|
* size changes on the view component. |
|
|
* |
|
|
* @return a ViewListener instance |
|
|
*/ |
|
|
protected ViewListener createViewListener() |
|
|
{ |
|
|
return new ViewListener(); |
|
|
} |
|
|
|
|
|
/** |
|
|
* Creates the LayoutManager that is used for this viewport. Override |
|
|
* this method if you want to use a custom LayoutManager. |
|
|
* |
|
|
* @return a LayoutManager to use for this viewport |
|
|
*/ |
|
|
protected LayoutManager createLayoutManager() |
|
|
{ |
|
|
return new ViewportLayout(); |
|
|
} |
|
|
|
|
|
/** |
|
569 |
* Scrolls the view so that contentRect becomes visible. |
* Scrolls the view so that contentRect becomes visible. |
570 |
* |
* |
571 |
* @param contentRect the rectangle to make visible within the view |
* @param contentRect the rectangle to make visible within the view |
618 |
accessibleContext = new AccessibleJViewport(); |
accessibleContext = new AccessibleJViewport(); |
619 |
return accessibleContext; |
return accessibleContext; |
620 |
} |
} |
621 |
|
|
622 |
|
protected void addImpl(Component comp, Object constraints, int index) |
623 |
|
{ |
624 |
|
if (getComponentCount() > 0) |
625 |
|
remove(getComponents()[0]); |
626 |
|
|
627 |
|
super.addImpl(comp, constraints, index); |
628 |
|
} |
629 |
|
|
630 |
|
protected void fireStateChanged() |
631 |
|
{ |
632 |
|
ChangeListener[] listeners = getChangeListeners(); |
633 |
|
for (int i = 0; i < listeners.length; ++i) |
634 |
|
listeners[i].stateChanged(changeEvent); |
635 |
|
} |
636 |
|
|
637 |
|
/** |
638 |
|
* Creates a {@link ViewListener} that is supposed to listen for |
639 |
|
* size changes on the view component. |
640 |
|
* |
641 |
|
* @return a ViewListener instance |
642 |
|
*/ |
643 |
|
protected ViewListener createViewListener() |
644 |
|
{ |
645 |
|
return new ViewListener(); |
646 |
|
} |
647 |
|
|
648 |
|
/** |
649 |
|
* Creates the LayoutManager that is used for this viewport. Override |
650 |
|
* this method if you want to use a custom LayoutManager. |
651 |
|
* |
652 |
|
* @return a LayoutManager to use for this viewport |
653 |
|
*/ |
654 |
|
protected LayoutManager createLayoutManager() |
655 |
|
{ |
656 |
|
return new ViewportLayout(); |
657 |
|
} |
658 |
|
|
659 |
|
/** |
660 |
|
* Computes the parameters for the blitting scroll method. <code>dx</code> |
661 |
|
* and <code>dy</code> specifiy the X and Y offset by which the viewport |
662 |
|
* is scrolled. All other arguments are output parameters and are filled by |
663 |
|
* this method. |
664 |
|
* |
665 |
|
* <code>blitFrom</code> holds the position of the blit rectangle in the |
666 |
|
* viewport rectangle before scrolling, <code>blitTo</code> where the blitArea |
667 |
|
* is copied to. |
668 |
|
* |
669 |
|
* <code>blitSize</code> holds the size of the blit area and |
670 |
|
* <code>blitPaint</code> is the area of the view that needs to be painted. |
671 |
|
* |
672 |
|
* This method returns <code>true</code> if blitting is possible and |
673 |
|
* <code>false</code> if the viewport has to be repainted completetly without |
674 |
|
* blitting. |
675 |
|
* |
676 |
|
* @param dx the horizontal delta |
677 |
|
* @param dy the vertical delta |
678 |
|
* @param blitFrom the position from where to blit; set by this method |
679 |
|
* @param blitTo the position where to blit area is copied to; set by this |
680 |
|
* method |
681 |
|
* @param blitSize the size of the blitted area; set by this method |
682 |
|
* @param blitPaint the area that needs repainting; set by this method |
683 |
|
* |
684 |
|
* @return <code>true</code> if blitting is possible, |
685 |
|
* <code>false</code> otherwise |
686 |
|
*/ |
687 |
|
protected boolean computeBlit(int dx, int dy, Point blitFrom, Point blitTo, |
688 |
|
Dimension blitSize, Rectangle blitPaint) |
689 |
|
{ |
690 |
|
if ((dx != 0 && dy != 0) || damaged) |
691 |
|
// We cannot blit if the viewport is scrolled in both directions at |
692 |
|
// once. |
693 |
|
return false; |
694 |
|
|
695 |
|
Rectangle portBounds = SwingUtilities.calculateInnerArea(this, getBounds()); |
696 |
|
|
697 |
|
// Compute the blitFrom and blitTo parameters. |
698 |
|
blitFrom.x = portBounds.x; |
699 |
|
blitFrom.y = portBounds.y; |
700 |
|
blitTo.x = portBounds.x; |
701 |
|
blitTo.y = portBounds.y; |
702 |
|
|
703 |
|
if (dy > 0) |
704 |
|
{ |
705 |
|
blitFrom.y = portBounds.y + dy; |
706 |
|
} |
707 |
|
else if (dy < 0) |
708 |
|
{ |
709 |
|
blitTo.y = portBounds.y - dy; |
710 |
|
} |
711 |
|
else if (dx > 0) |
712 |
|
{ |
713 |
|
blitFrom.x = portBounds.x + dx; |
714 |
|
} |
715 |
|
else if (dx < 0) |
716 |
|
{ |
717 |
|
blitTo.x = portBounds.x - dx; |
718 |
|
} |
719 |
|
|
720 |
|
// Compute size of the blit area. |
721 |
|
if (dx != 0) |
722 |
|
{ |
723 |
|
blitSize.width = portBounds.width - Math.abs(dx); |
724 |
|
blitSize.height = portBounds.height; |
725 |
|
} |
726 |
|
else if (dy != 0) |
727 |
|
{ |
728 |
|
blitSize.width = portBounds.width; |
729 |
|
blitSize.height = portBounds.height - Math.abs(dy); |
730 |
|
} |
731 |
|
|
732 |
|
// Compute the blitPaint parameter. |
733 |
|
blitPaint.setBounds(portBounds); |
734 |
|
if (dy > 0) |
735 |
|
{ |
736 |
|
blitPaint.y = portBounds.y + portBounds.height - dy; |
737 |
|
blitPaint.height = dy; |
738 |
|
} |
739 |
|
else if (dy < 0) |
740 |
|
{ |
741 |
|
blitPaint.height = -dy; |
742 |
|
} |
743 |
|
if (dx > 0) |
744 |
|
{ |
745 |
|
blitPaint.x = portBounds.x + portBounds.width - dx; |
746 |
|
blitPaint.width = dx; |
747 |
|
} |
748 |
|
else if (dx < 0) |
749 |
|
{ |
750 |
|
blitPaint.width = -dx; |
751 |
|
} |
752 |
|
|
753 |
|
return true; |
754 |
|
} |
755 |
|
|
756 |
|
/** |
757 |
|
* Paints the viewport in case we have a scrollmode of |
758 |
|
* {@link #SIMPLE_SCROLL_MODE}. |
759 |
|
* |
760 |
|
* This simply paints the view directly on the surface of the viewport. |
761 |
|
* |
762 |
|
* @param g the graphics context to use |
763 |
|
*/ |
764 |
|
void paintSimple(Graphics g) |
765 |
|
{ |
766 |
|
Point pos = getViewPosition(); |
767 |
|
Component view = getView(); |
768 |
|
boolean translated = false; |
769 |
|
try |
770 |
|
{ |
771 |
|
g.translate(-pos.x, -pos.y); |
772 |
|
translated = true; |
773 |
|
view.paint(g); |
774 |
|
} |
775 |
|
finally |
776 |
|
{ |
777 |
|
if (translated) |
778 |
|
g.translate (pos.x, pos.y); |
779 |
|
} |
780 |
|
} |
781 |
|
|
782 |
|
/** |
783 |
|
* Paints the viewport in case we have a scroll mode of |
784 |
|
* {@link #BACKINGSTORE_SCROLL_MODE}. |
785 |
|
* |
786 |
|
* This method uses a backing store image to paint the view to, which is then |
787 |
|
* subsequently painted on the screen. This should make scrolling more |
788 |
|
* smooth. |
789 |
|
* |
790 |
|
* @param g the graphics context to use |
791 |
|
*/ |
792 |
|
void paintBackingStore(Graphics g) |
793 |
|
{ |
794 |
|
// If we have no backing store image yet or the size of the component has |
795 |
|
// changed, we need to rebuild the backing store. |
796 |
|
if (backingStoreImage == null || damaged) |
797 |
|
{ |
798 |
|
backingStoreImage = createImage(getWidth(), getHeight()); |
799 |
|
Graphics g2 = backingStoreImage.getGraphics(); |
800 |
|
paintSimple(g2); |
801 |
|
g2.dispose(); |
802 |
|
} |
803 |
|
// Otherwise we can perform the blitting on the backing store image: |
804 |
|
// First we move the part that remains visible after scrolling, then |
805 |
|
// we only need to paint the bit that becomes newly visible. |
806 |
|
else |
807 |
|
{ |
808 |
|
Graphics g2 = backingStoreImage.getGraphics(); |
809 |
|
Point viewPosition = getViewPosition(); |
810 |
|
int dx = viewPosition.x - lastPaintPosition.x; |
811 |
|
int dy = viewPosition.y - lastPaintPosition.y; |
812 |
|
boolean canBlit = computeBlit(dx, dy, cachedBlitFrom, cachedBlitTo, |
813 |
|
cachedBlitSize, cachedBlitPaint); |
814 |
|
if (canBlit) |
815 |
|
{ |
816 |
|
// Copy the part that remains visible during scrolling. |
817 |
|
g2.copyArea(cachedBlitFrom.x, cachedBlitFrom.y, |
818 |
|
cachedBlitSize.width, cachedBlitSize.height, |
819 |
|
cachedBlitTo.x - cachedBlitFrom.x, |
820 |
|
cachedBlitTo.y - cachedBlitFrom.y); |
821 |
|
// Now paint the part that becomes newly visible. |
822 |
|
g2.setClip(cachedBlitPaint.x, cachedBlitPaint.y, |
823 |
|
cachedBlitPaint.width, cachedBlitPaint.height); |
824 |
|
paintSimple(g2); |
825 |
|
} |
826 |
|
// If blitting is not possible for some reason, fall back to repainting |
827 |
|
// everything. |
828 |
|
else |
829 |
|
{ |
830 |
|
paintSimple(g2); |
831 |
|
} |
832 |
|
g2.dispose(); |
833 |
|
} |
834 |
|
// Actually draw the backingstore image to the graphics context. |
835 |
|
g.drawImage(backingStoreImage, 0, 0, this); |
836 |
|
// Update the lastPaintPosition so that we know what is already drawn when |
837 |
|
// we paint the next time. |
838 |
|
lastPaintPosition.setLocation(getViewPosition()); |
839 |
|
} |
840 |
|
|
841 |
|
/** |
842 |
|
* Paints the viewport in case we have a scrollmode of |
843 |
|
* {@link #BLIT_SCROLL_MODE}. |
844 |
|
* |
845 |
|
* This paints the viewport using a backingstore and a blitting algorithm. |
846 |
|
* Only the newly exposed area of the view is painted from the view painting |
847 |
|
* methods, the remainder is copied from the backing store. |
848 |
|
* |
849 |
|
* @param g the graphics context to use |
850 |
|
*/ |
851 |
|
void paintBlit(Graphics g) |
852 |
|
{ |
853 |
|
// We cannot perform blitted painting as it is described in Sun's API docs. |
854 |
|
// There it is suggested that this painting method should blit directly |
855 |
|
// on the parent window's surface. This is not possible because when using |
856 |
|
// Swing's double buffering (at least our implementation), it would |
857 |
|
// immediatly be painted when the buffer is painted on the screen. For this |
858 |
|
// to work we would need a kind of hole in the buffer image. And honestly |
859 |
|
// I find this method not very elegant. |
860 |
|
// The alternative, blitting directly on the buffer image, is also not |
861 |
|
// possible because the buffer image gets cleared everytime when an opaque |
862 |
|
// parent component is drawn on it. |
863 |
|
|
864 |
|
// What we do instead is falling back to the backing store approach which |
865 |
|
// is in fact a mixed blitting/backing store approach where the blitting |
866 |
|
// is performed on the backing store image and this is then drawn to the |
867 |
|
// graphics context. This is very robust and works independent of the |
868 |
|
// painting mechanism that is used by Swing. And it should have comparable |
869 |
|
// performance characteristics as the blitting method. |
870 |
|
paintBackingStore(g); |
871 |
|
} |
872 |
} |
} |