# zoomen in JPanel



## JVolker (19. Jul 2012)

Hallo,

ich bin noch ein relativer Neuling in der Java Welt bin habe jetzt eine JTable programmiert in der man X/Y Koordinaten eingeben kann und diese werden als Punkt in einem JPanel dargestellt.

Jetzt würde ich gerne in mein JPanel reinzoomen und natürlich auch wieder rauszoomen können. Bei meiner bisherigen Suche habe ich noch nix gefunden was ich bei mir umsetzen konnte. Erster versuch war Panel als Bild umzuwandeln und dann auf einem Bild zu zoomen. Nummer 2 ist momentan Piccolo2D nur lider komm ich damit noch nicht klar. Hat jemand ein kleines Beispiel??? 
(JPanel mit Mausrad zoomen?)


----------



## vanny (19. Jul 2012)

Naja, das JPanel Zoomen wird so nicht klappen, aber das, was auf dem JPanel gezeichnet wird kannst du natürlich in Abhämgigkeit vom Zoomfaktor darstellen lassen.
Hier bietet es sich evtl. an, einen Vieport zu schaffen, der das übernimmt.

Fazit:
1. Schau dir mal an, wie man auf einem JPanel zeichnet.
2. Lies mal ein bisschen im Netz, was ein Viewport ist und wie dieser funktioniert.
3. Für sowas benötigst du eigentlich kein externes Framework, das geht auch gut mit standardjava.

Gruß Vanny


----------



## André Uhres (21. Jul 2012)

vanny hat gesagt.:


> Du willst anders sein? Ok ! Nur ich hab nichts davon((
> pro - Konvention
> 
> Bester Ratschlag Ever: SEI KREATIV !!



[offtopic]
Es ist nichts falsch daran, mit Normen zu brechen, wenn es aus gutem Grund ist. Wenn Du es selbst machst, und es Dir zum Vorteil gereicht, hast Du auch etwas davon! Übrigens: wer kreativ sein will, muss manchmal auch mit Normen brechen können!
[/offtopic]

Gruß,
André


----------



## vanny (21. Jul 2012)

???:L


----------



## Marco13 (21. Jul 2012)

Wie brauchen auch ein OnTopic-Tag. Vielleicht eins, bei dem die Schrift größer und kontrastreicher Dargestellt wird :reflect:

Wenn es NUR um die Darstellung von Punkten geht, und die Anzahl der Punkte nicht gerade in die hunderttausende geht, kann man das relativ leicht mit einem eigenen JPanel erreichen. Es gibt allerdings ein paar Freiheitsgrade, wie genau das aussehen und sich verhalten soll, auch im Zusammenhang mit der Frage, welche weiteren Anforderungen es gibt. Soll man die Punkte dann noch anklicken und/oder verschieben können (und wenn ja: Soll dann die Tabelle entsprechend aktualisiert werden), oder bewegen sich die Punkte vielleicht von selbst, oder wird noch mehr gezeichnet als nur Punkte...? Haben die Punkte Farben oder unterschiedliche Größen...?
Ein reines raus-dumpen von fixen Punkten, die "irgendwie" gezeichnet werden sollten, und in denen man "irgendwie" rum-browsen kann, ist mit einer überschriebenen paintComponent und einem MouseListener nicht sooo aufwändig.


----------



## vanny (21. Jul 2012)

Marco13 hat gesagt.:


> Wie brauchen auch ein OnTopic-Tag. Vielleicht eins, bei dem die Schrift größer und kontrastreicher Dargestellt wird :reflect: ...



Ich hoffe, bitte, bete dafür, das da nichts anderes als Sarkasmus von dir kam:autsch::shock:


----------



## Marco13 (21. Jul 2012)

Ein Seitenhieb auf die Diskussion um http://www.java-forum.org/plauderecke/79231-verbesserungsvorschlaege-99.html#post920631 herum


----------



## Paddelpirat (21. Jul 2012)

Mit JavaFX würde das ganz einfach gehen, weil jede Komponente eine setScaleX bzw. setScaleY Methode hat. (Nur mal so als Info)


----------



## André Uhres (22. Jul 2012)

Marco13 hat gesagt.:


> Ein Seitenhieb auf die Diskussion um http://www.java-forum.org/plauderecke/79231-verbesserungsvorschlaege-99.html#post920631 herum



Ziemlich überspitzt, oder? 

Gruß,
André


----------



## Marco13 (22. Jul 2012)

Ja, finde ich auch. 


(Oder meintest du meinen Seitenhieb?  )


----------



## André Uhres (23. Jul 2012)

Marco13 hat gesagt.:


> (Oder meintest du meinen Seitenhieb?  )



Nein, Du hast mich schon richtig verstanden .

Gruß,
André


----------



## Guybrush Threepwood (23. Jul 2012)

Zurück zum Thema: Es ist nicht genau, was Du suchst. Vielleicht hilft es Dir trotzdem. Im unten stehenden  Code gibt es einen Lupen-Funktion, die einen Teil des Panels zoomed. Den Code habe ich vor längerem im Forum hier gefunden und leicht modifiziert (Autor weiß ich leider nicht mehr, sorry!). Es ist kein voller Zoom, sondern nur ausschnittsweise:


```
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.RoundRectangle2D;
import java.net.MalformedURLException;
import java.net.URL;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.event.MouseInputListener;
 
public class ZoomPanel extends JPanel implements MouseInputListener {
    private static final int DIMENSION = 100;
    private static final double ZOOM = 3;
    
    private Shape s;
    
    public ZoomPanel() {
        addMouseListener(this);
        addMouseMotionListener(this);
        setLayout(new GridLayout());
        try {
            add(new JLabel(new ImageIcon(new URL("http://www.java-forum.org/images/misc/java_forum_org.gif"))));
        } catch (final MalformedURLException e) {
            throw new RuntimeException(e);
        }
    }
    
    public ZoomPanel(LayoutManager layout) {
        addMouseListener(this);
        addMouseMotionListener(this);
        setLayout(layout);
        try {
            add(new JLabel(new ImageIcon(new URL("http://www.java-forum.org/images/misc/java_forum_org.gif"))));
        } catch (final MalformedURLException e) {
            throw new RuntimeException(e);
        }
    }
    
    @Override
    public void paint(final Graphics g) {
        super.paint(g);
        if (s != null) {
            final Graphics2D g2 = (Graphics2D) g.create();
            g2.setClip(s);
            final Rectangle b = s.getBounds();
            g2.clearRect(b.x, b.y, b.width, b.height);
            final AffineTransform transform = new AffineTransform();
            final double centerX = b.getCenterX();
            final double centerY = b.getCenterY();
            transform.translate(centerX, centerY);
            transform.scale(ZOOM, ZOOM);
            transform.translate(-centerX, -centerY);
            g2.setTransform(transform);
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            super.paint(g2);
            g2.setTransform(new AffineTransform());
            g2.setClip(g.getClip());
            g2.draw(s);
            g2.dispose();
        }
    }
    
    private void moveShape(final MouseEvent e) {
        final Point p = e.getPoint();
        final int hd = DIMENSION ;
        s = new RoundRectangle2D.Double(p.x - hd, p.y - hd, 3*DIMENSION, DIMENSION, 20d, 20d);
        repaint();
    }
    
    @Override
    public void mousePressed(final MouseEvent e) {
        if (SwingUtilities.isRightMouseButton(e)) {
        	setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        			
            moveShape(e);
        }
    }
    
    @Override
    public void mouseReleased(final MouseEvent e) {
        if (SwingUtilities.isRightMouseButton(e)) {
        	setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
            s = null;
            repaint();
        }
    }
    
    @Override
    public void mouseDragged(final MouseEvent e) {
        if (SwingUtilities.isRightMouseButton(e)) {
            moveShape(e);
        }
    }
    
    @Override
    public void mouseClicked(final MouseEvent e) {}
    
    @Override
    public void mouseEntered(final MouseEvent e) {}
    
    @Override
    public void mouseExited(final MouseEvent e) {}
    
    @Override
    public void mouseMoved(final MouseEvent e) {}
    
    public static void main(final String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                final JFrame frame = new JFrame("ZoomPanel");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setContentPane(new OldZoomPanel());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
}
```


----------



## bERt0r (23. Jul 2012)

Geht auch einfacher:

```
panel = new JPanel()
		{
			@Override
			public void paintComponent(Graphics g)
			{
				super.paintComponent(g);
				Graphics2D g2=(Graphics2D)g;
				g2.scale(zoomFaktor,zoomFaktor);
			}
			
			public Dimension getPreferredSize()
			{
				Dimension d=super.getPreferredSize();
				d.height=(int) (d.height*zoomFaktor);
				d.width=(int) (d.width*zoomFaktor);
				return d;
			}
		};
```


```
import java.awt.BorderLayout;


public class ZoomDemo extends JFrame
{
	
	private JPanel contentPane;
	private JScrollPane scrollPane;
	private JPanel panel;
	private float zoomFaktor=1.5f;
	private JLabel lblHalloWelt;
	private JLabel lblImage;
	
	/**
	 * Launch the application.
	 */
	public static void main(String[] args)
	{
		EventQueue.invokeLater(new Runnable()
			{
				public void run()
				{
					try
					{
						ZoomDemo frame = new ZoomDemo();
						frame.setVisible(true);
					} catch (Exception e)
					{
						e.printStackTrace();
					}
				}
			});
	}
	
	/**
	 * Create the frame.
	 */
	public ZoomDemo()
	{
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(100, 100, 450, 300);
		contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		contentPane.setLayout(new BorderLayout(0, 0));
		setContentPane(contentPane);
		
		scrollPane = new JScrollPane();
		contentPane.add(scrollPane, BorderLayout.CENTER);
		
		panel = new JPanel()
		{
			@Override
			public void paintComponent(Graphics g)
			{
				super.paintComponent(g);
				Graphics2D g2=(Graphics2D)g;
				g2.scale(zoomFaktor,zoomFaktor);
			}
			
			public Dimension getPreferredSize()
			{
				Dimension d=super.getPreferredSize();
				d.height=(int) (d.height*zoomFaktor);
				d.width=(int) (d.width*zoomFaktor);
				return d;
			}
		};
		FlowLayout flowLayout = (FlowLayout) panel.getLayout();
		flowLayout.setAlignment(FlowLayout.LEFT);
		panel.setBorder(new LineBorder(new Color(0, 0, 0)));
		
		panel.addMouseWheelListener(new MouseWheelListener()
		{

			@Override
			public void mouseWheelMoved(MouseWheelEvent e)
			{
				zoomFaktor+=e.getPreciseWheelRotation()/10;
				panel.revalidate();
			}
			
		});
		scrollPane.setViewportView(panel);
		
		lblHalloWelt = new JLabel("Hallo Welt");
		panel.add(lblHalloWelt);
		
		lblImage = new JLabel();
		try
		{
			lblImage.setIcon(new ImageIcon(new URL("http://www.java-forum.org/images/misc/java_forum_org.gif")));
		} catch (MalformedURLException e1)
		{
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		panel.add(lblImage);
	}
	
}
```


----------



## JVolker (24. Jul 2012)

Hallo,

Danke für die ganzen Hinweise. Ich probiere mal ein paar Sachen aus. 

@Marco13 Bei mir sind es momentan nur Punkte und Linien welche dargestellt werden und der selektierte Punkt aus der Jtable wird mit einem Fadenkreuz angezeigt. Hinzu kommt noch das ein MouseListener einen Click auf einen Punk bemerkt und das Fadenkreuz wandert.

Ich stelle mir jetzt zwei Varianten vor:

1. ganz einfach mit dem Mouserad rein bwz. rauszoomen. 

2. man hat so eine art WorldView und bekommt in einem extra Fenster einen gezoomten ausschnitt angezeigt.

Mein momentanes Problem ist das ich momentan leicht verwirrt bin, da es anscheinend sehr viele Lösungen für mein Problem gibt. Meine Frage wäre jetzt was ist für einen kleinen Anfänger wie mich das einfachste??

Danke


----------



## Marco13 (24. Jul 2012)

Ich weiß schon, warum ich immer so nachfrage...  Wenn man Punkte oder Linien einfach durch Skalieren von Graphics2D größer malt, werden sie auch dicker - d.h. eine einfache Linie ist bei einem Zoom von 10 dann eben 10 Pixel dick, und das ist oft nicht das, was man will. Dazu kommt, dass du ja mit Sicherheit nicht nur Zoomen willst, sondern auch verschieben. Dann kommt die übliche Minimalanforderung: Wenn man zoomt, soll man bitteschön da hin zoomen, wo auch die Maus gerade ist! (Jaaa, es IST trivial, aber ich mach' die Skalierung IMMER falschrum, auch wenn ich schon vorher berücksichtige, dass ich sie falsch machen werde ;(  ). Wenn man dann noch Sachen anklicken will, muss man die ganzen Transformationen ja auch wieder "zurückrechnen", um von Bildschirmkoordinaten auf die entsprechenden Weltkoordinaten zu kommen. Ein Übersichtsfenster ist dann nochmal was, was zwar nicht wirklich "schwer" ist, aber auch erstmal hingeschrieben sein muss. (Vielleicht soll dort der akutelle Ausschnitt als kleines Rechteck angezeigt werden, das man auch wieder direkt verschieben kann... :reflect: ). Die Verbindung zur JTable, und ein Fadenkreuz, das "wandern" soll (vielleicht mit einer hübschen Animation?) sind dann noch Sahnehäubchen.

Ich wollte schon ewig mal so eine allgemeine Component zum Zoomen und Pannen schreiben. Man braucht es immer wieder, und jedes mal schreibt man's "neu" (und jedesmal mach' ich das mit der Skalierung falsch ;(  ), aber man müßte es eben EINmal "richtig" machen. Und dieses "richtig" macht das ganze dann u.U. etwas aufwändiger. Auch wenn meine Liste mit "Ich wollte ja schon ewig mal..."-Punkten inzwischen so lang ist, dass ich sie mit ZIP komprimiere, um Festplattenplatz zu sparen: Vielleicht schau' ich da nochmal. Vielleicht postet aber auch bis morgen schon jemand eine "ausreichende" Lösung...


----------



## Marco13 (26. Jul 2012)

Offenbar nicht. Hab' mal schnell ein Snippet hingeschrieben. Ist natürlich auch (wieder) nicht das "Silver Bullet das alles kann", aber kann vielleicht als Ausgangspunkt dienen...

```
package de.javagl.snippets.viewer;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.swing.JPanel;
import javax.swing.event.MouseInputAdapter;

/**
 * A panel that allows panning and zooming. It contains a "Painter"
 * interface. Instances of classes implementing this interface
 * may be added to this viewer and perform painting operations,
 * depending on the current zoom and translation of the Viewer.
 * Additionally, it offers a "Transformer" interface, which 
 * offers methods for the conversion between screen- and world
 * coordinates.
 */
public class Viewer extends JPanel 
{
    /**
     * Interface for all classes that may perform painting  operations. 
     * Instances of classes implementing this interface may be passed 
     * to the {@link Viewer#addPainter(Painter)} method. 
     */
    public interface Painter
    {
        /**
         * Perform the painting operations on the given Graphics.
         * The given AffineTransform will describe the current 
         * zooming and translation of this Viewer.
         * 
         * @param g The Graphics used for painting 
         * @param at The AffineTransform containing the current
         * zooming and translation of this Viewer.
         * @param w The width of the painting area
         * @param h The height of the painting area
         */
        void paint(Graphics2D g, AffineTransform at, int w, int h);
    }
    
    /**
     * Interface for transforming between the screen- and world coordinate
     * system of this Viewer, depending on its current zooming and 
     * translation. An instance of this class may be obtained 
     * from {@link Viewer#getTransformer()}.
     */
    public interface Transformer
    {
        /**
         * Convert the given world coordinate into a screen coordinate
         * 
         * @param x The world coordinate
         * @return The screen screen
         */
        double worldToScreenX(double x);

        /**
         * Convert the given world coordinate into a screen coordinate
         * 
         * @param y The world coordinate
         * @return The screen screen
         */
        double worldToScreenY(double y);

        /**
         * Convert the given screen coordinate into a world coordinate
         * 
         * @param x The screen coordinate
         * @return The world coordinate
         */
        double screenToWorldX(double x);

        /**
         * Convert the given screen coordinate into a world coordinate
         * 
         * @param y The screen coordinate
         * @return The world coordinate
         */
        double screenToWorldY(double y);
        
        /**
         * Convert the given point from screen coordinates to 
         * world coordinates. If the given destination point 
         * is <code>null</code>, a new point will be created
         * and returned.
         * 
         * @param pSrc The source point
         * @param pDst The destination point
         * @return The destination point
         */
        Point2D screenToWorld(Point2D pSrc, Point2D pDst);

        /**
         * Convert the given point from world coordinates to 
         * screen coordinates. If the given destination point 
         * is <code>null</code>, a new point will be created
         * and returned.
         * 
         * @param pSrc The source point
         * @param pDst The destination point
         * @return The destination point
         */
        Point2D worldToScreen(Point2D pSrc, Point2D pDst);
    }
    

    /**
     * Serial UID
     */
    private static final long serialVersionUID = -3252732941609348700L;
    
    /**
     * The current zooming speed
     */
    private double zoomingSpeed = 0.1;
    
    /**
     * The current offset (translation) in x-direction,
     * in world units
     */
    private double offsetX = 0;

    /**
     * The current offset (translation) in y-direction,
     * in world units
     */
    private double offsetY = 0;
    
    /**
     * The current scaling in x-direction
     */
    private double scaleX = 1;

    /**
     * The current scaling in y-direction
     */
    private double scaleY = 1;
    
    /**
     * The previous mouse position
     */
    private final Point previousPoint;
    
    /**
     * The {@link Painter}s that will perform painting operations in 
     * the {@link #paintComponent(Graphics)} method.
     */
    private final List<Painter> painters;
    
    /**
     * The {@link Transformer} instance of this Viewer
     */
    private final Transformer transformer;
    
    /**
     * A class summarizing the mouse interaction for this viewer.
     */
    private class MouseControl extends MouseInputAdapter 
        implements MouseListener, MouseMotionListener, MouseWheelListener
    {
        @Override
        public void mouseWheelMoved(MouseWheelEvent e)
        {
            zoom(e.getX(), e.getY(), e.getWheelRotation() * zoomingSpeed);
        }
        
        @Override
        public void mouseDragged(MouseEvent e)
        {
            int dx = e.getX() - previousPoint.x;
            int dy = e.getY() - previousPoint.y;
            translate(dx, dy);
            previousPoint.setLocation(e.getX(), e.getY());
        }

        @Override
        public void mouseMoved(MouseEvent e)
        {
            previousPoint.setLocation(e.getX(), e.getY());
        }
    }
    
    /**
     * Simple implementation of a {@link Transformer}
     */
    class SimpleTransformer implements Transformer
    {
        @Override
        public double worldToScreenX(double x)
        {
            return (x + offsetX) * scaleX;
        }

        @Override
        public double worldToScreenY(double y)
        {
            return (y + offsetY) * scaleY;
        }
        
        @Override
        public double screenToWorldX(double x)
        {
            return x / scaleX - offsetX;
        }
        
        @Override
        public double screenToWorldY(double y)
        {
            return y / scaleY - offsetY;
        }
        
        @Override
        public Point2D screenToWorld(Point2D pSrc, Point2D pDst)
        {
            double x = screenToWorldX(pSrc.getX());
            double y = screenToWorldY(pSrc.getY());
            if (pDst == null)
            {
                pDst = new Point2D.Double(x, y);
            }
            else
            {
                pDst.setLocation(x, y);
            }
            return pDst;
        }

        @Override
        public Point2D worldToScreen(Point2D pSrc, Point2D pDst)
        {
            double x = worldToScreenX(pSrc.getX());
            double y = worldToScreenY(pSrc.getY());
            if (pDst == null)
            {
                pDst = new Point2D.Double(x, y);
            }
            else
            {
                pDst.setLocation(x, y);
            }
            return pDst;
        }
        
    };
    
    
    /**
     * Creates a new Viewer.
     */
    public Viewer()
    {
        painters = new CopyOnWriteArrayList<Painter>();
        previousPoint = new Point();
        transformer = new SimpleTransformer();

        MouseControl mouseControl = new MouseControl();
        addMouseListener(mouseControl);
        addMouseMotionListener(mouseControl);
        addMouseWheelListener(mouseControl);
        
        setBackground(Color.WHITE);
    }
    
    /**
     * Set the zooming speed of this viewer, which will affect the
     * change of the scaling depending on the mouse wheel rotation
     * 
     * @param zoomingSpeed The zooming speed
     */
    public void setZoomingSpeed(double zoomingSpeed)
    {
        this.zoomingSpeed = zoomingSpeed;
    }
    
    /**
     * Add the given {@link Painter}, which will perform painting
     * operations in the {@link #paintComponent(Graphics)} method.
     * 
     * @param painter The {@link Painter} to add
     */
    public void addPainter(Painter painter)
    {
        painters.add(painter);
        repaint();
    }
    
    /**
     * Remove the given {@link Painter}
     * 
     * @param painter The {@link Painter} to remove
     */
    public void removePainter(Painter painter)
    {
        painters.remove(painter);
        repaint();
    }

    /**
     * Return the {@link Transformer} that offers coordinate 
     * transformations for this Viewer
     * 
     * @return The {@link Transformer} for this Viewer
     */
    public Transformer getTransformer()
    {
        return transformer;
    }
    

    @Override
    protected void paintComponent(Graphics gr)
    {
        super.paintComponent(gr);
        Graphics2D g = (Graphics2D)gr;
        
        AffineTransform at = new AffineTransform();
        at.scale(scaleX, scaleY);
        at.translate(offsetX, offsetY);

        for (Painter painter : painters)
        {
            painter.paint(g, at, getWidth(), getHeight());
        }
    }
    
    /**
     * Zoom about the specified point (in screen coordinates)
     * by the given amount
     * 
     * @param x The x-coordinate of the zooming center
     * @param y The y-coordinate of the zooming center
     * @param amount The zooming amount
     */
    private void zoom(int x, int y, double amount)
    {
        offsetX -= x / scaleX;
        offsetY -= y / scaleY;
        scaleX += amount * scaleX;
        scaleY += amount * scaleY;
        offsetX += x / scaleX;
        offsetY += y / scaleY;
        repaint();
    }
    
    /**
     * Translate this viewer by the given delta, in screen
     * coordinates
     * 
     * @param dx The movement delta in x-direction
     * @param dy The movement delta in y-direction
     */
    private void translate(int dx, int dy)
    {
        offsetX += dx / scaleX;
        offsetY += dy / scaleY;
        repaint();
    }

}
```


Und ein kleiner Test, der auch nochmal den Unterschied zwischen einer skalierten Graphics2D und skalierten Objekten demonstriert:


```
package de.javagl.snippets.viewer;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Locale;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;

import de.javagl.snippets.viewer.Viewer.Painter;
import de.javagl.snippets.viewer.Viewer.Transformer;


/**
 * Simple test and demonstration of the Viewer class
 */
public class ViewerTest
{
    /**
     * The entry point of this test
     * 
     * @param args Not used
     */
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            
            @Override
            public void run()
            {
                createAndShowGUI();
            }
        });
    }
    
    /**
     * Create and show the GUI, to be called on the EDT
     */
    private static void createAndShowGUI()
    {
        JFrame f = new JFrame("Viewer");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        f.getContentPane().setLayout(new BorderLayout());
        
        f.getContentPane().add(
            new JLabel("Mouse drags to pan, mouse wheel to zoom"), 
            BorderLayout.NORTH);
        
        final Viewer viewerComponent = new Viewer();
        viewerComponent.addPainter(new TestPainterGraphics());
        viewerComponent.addPainter(new TestPainterShapes());
        f.getContentPane().add(viewerComponent, BorderLayout.CENTER);

        final JLabel infoLabel = new JLabel("");
        f.getContentPane().add(infoLabel, BorderLayout.SOUTH);
        
        // Add a mouse motion listener to the viewer, which 
        // will update the info label containing the current
        // mouse position in screen- and world coordinates
        viewerComponent.addMouseMotionListener(new MouseMotionListener()
        {
            @Override
            public void mouseMoved(MouseEvent e)
            {
                updateInfo(e.getPoint());
            }
            
            @Override
            public void mouseDragged(MouseEvent e)
            {
                updateInfo(e.getPoint());
            }
            
            /**
             * Update the info label depending on the given screen point
             * @param screenPoint The screen point
             */
            private void updateInfo(Point screenPoint)
            {
                Transformer transformer = viewerComponent.getTransformer();
                Point2D worldPoint = 
                    transformer.screenToWorld(screenPoint, null);
                infoLabel.setText(
                    "Screen: "+format(screenPoint)+
                    " World: "+format(worldPoint));
            }

            /**
             * Create a simple string representation of the given point
             * 
             * @param p The point
             * @return The string representation
             */
            private String format(Point2D p)
            {
                String xs = String.format(Locale.ENGLISH, "%.2f", p.getX());
                String ys = String.format(Locale.ENGLISH, "%.2f", p.getY());
                return "("+xs+","+ys+")";
            }
            
        });
        
        f.setSize(500,500);
        f.setVisible(true);
    }
    
    /**
     * Implementation of the {@link Painter} interface that 
     * shows some painting in a transformed Graphics2D
     */
    private static class TestPainterGraphics implements Painter
    {
        @Override
        public void paint(Graphics2D g, AffineTransform at, int w, int h)
        {
            g.setRenderingHint(
                RenderingHints.KEY_ANTIALIASING, 
                RenderingHints.VALUE_ANTIALIAS_ON);
            
            
            Path2D grid = new Path2D.Double();
            for (int x=0; x<100; x+=10)
            {
                for (int y=0; y<50; y+=10)
                {
                    grid.moveTo(x, y);
                    grid.lineTo(x+10, y);
                    grid.moveTo(x, y);
                    grid.lineTo(x, y+10);
                }
            }
            Rectangle2D border = new Rectangle2D.Double(0,0,100,50);
            
            AffineTransform oldAT = g.getTransform();
            g.transform(at);

            g.setColor(Color.LIGHT_GRAY);
            g.draw(grid);
            g.setColor(Color.BLACK);
            g.draw(border);
            g.setColor(Color.RED);
            g.drawString("This is painted", 10, 15);
            g.drawString("by transforming", 10, 30);
            g.drawString("the Graphics2D", 10, 45);

            g.setTransform(oldAT);
        }
    }    
    
    /**
     * Implementation of the {@link Painter} interface that 
     * shows some painting of transformed Shapes
     */
    private static class TestPainterShapes implements Painter
    {
        @Override
        public void paint(Graphics2D g, AffineTransform at, int w, int h)
        {
            g.setRenderingHint(
                RenderingHints.KEY_ANTIALIASING, 
                RenderingHints.VALUE_ANTIALIAS_ON);
            
            
            Path2D grid = new Path2D.Double();
            for (int x=0; x<100; x+=10)
            {
                for (int y=50; y<100; y+=10)
                {
                    grid.moveTo(x, y);
                    grid.lineTo(x+10, y);
                    grid.moveTo(x, y);
                    grid.lineTo(x, y+10);
                }
            }
            Rectangle2D border = new Rectangle2D.Double(0,50,100,50);
            
            g.setColor(Color.LIGHT_GRAY);
            g.draw(at.createTransformedShape(grid));
            g.setColor(Color.BLACK);
            g.draw(at.createTransformedShape(border));
            g.setColor(Color.RED);
            
            g.fill(createText("This is painted", g.getFont(), at, 10, 65));
            g.fill(createText("by transforming", g.getFont(), at, 10, 80));
            g.fill(createText("the shapes", g.getFont(), at, 10, 95));
        }
        
        /**
         * Create a shape of the specified text. Caution, this is a
         * very inefficient solution!
         * 
         * @param text The text
         * @param font The font
         * @param at The AffineTransform
         * @param x The x-coordinate
         * @param y The y-coordinate
         * @return The shape of the text
         */
        private static Shape createText(
            String text, Font font, AffineTransform at, int x, int y)
        {
            FontRenderContext fontRenderContext = 
                new FontRenderContext(null, true, true);       
            GlyphVector glyphVector = 
                font.createGlyphVector(fontRenderContext, text);
            AffineTransform fullAt = new AffineTransform(at);
            fullAt.translate(x, y);
            return fullAt.createTransformedShape(glyphVector.getOutline());
        }
    }
}
```


----------

