/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2008 - 2009, Johann Sorel
* (C) 2009 - 2013, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.gui.swing.render2d.decoration;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.RadialGradientPaint;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Area;
import java.awt.geom.Line2D;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JComponent;
import org.geotoolkit.gui.swing.render2d.JMap2D;
import org.apache.sis.util.logging.Logging;
import org.geotoolkit.display.canvas.AbstractCanvas2D;
/**
*
* @author johann sorel (Puzzle-GIS)
* @module
*/
public class JClassicNavigationDecoration extends JComponent implements MapDecoration {
public static enum THEME{
CLASSIC,
NEO
}
private static final Logger LOGGER = Logging.getLogger("org.geotoolkit.gui.swing.render2d.decoration");
private JMap2D map = null;
private static final int MIN_SIZE = 20;
private static final int CERCLE_WIDTH = 100;
private static final int MARGIN = CERCLE_WIDTH / 10;
private boolean minimized = false;
private final Color teinte = Color.GRAY.brighter();
private final Color north = Color.RED;
private final Color teinteDark;
private final Color teinteLight;
private final Color text1;
private final Color text2;
private final Color dark = Color.BLACK;
private final Color base;
private final Color trans = new Color(1f, 1f, 1f, 0f);
private final boolean inBorder;
private double rotation = 0;
private Shape innerCercle;
private Shape outerCercle;
private Shape arrow;
private Shape resetShape;
private final BufferedImage buffer = new BufferedImage(MARGIN + CERCLE_WIDTH+1, MARGIN + CERCLE_WIDTH+1, BufferedImage.TYPE_INT_ARGB);
private boolean mustUpdate = true;
/**
* 0 = drag scale
* 1 = drag rotation
* -1 = no drag
*/
private short actionFlag = -1;
/**
* -1 = no button
* 0 = top
* 1 = right
* 2 = down
* 3 = left
*/
private short overButton = -1;
public JClassicNavigationDecoration(){
this(THEME.CLASSIC);
}
public JClassicNavigationDecoration(final THEME theme) {
if(theme == THEME.CLASSIC){
teinteDark = Color.GRAY;
teinteLight = Color.WHITE;
text1 = Color.GRAY;
text2 = Color.LIGHT_GRAY;
base = Color.WHITE;
inBorder = true;
}else{
teinteDark = teinte.darker();
teinteLight = teinte.brighter();
text1 = teinteDark;
text2 = teinteLight;
base = Color.BLACK;
inBorder = false;
}
addMouseListener(mouseListener);
addMouseMotionListener(mouseMotionListener);
setOpaque(false);
innerCercle = new java.awt.geom.Ellipse2D.Float(3 * MARGIN, 3 * MARGIN, CERCLE_WIDTH - 4 * MARGIN, CERCLE_WIDTH - 4 * MARGIN);
outerCercle = new java.awt.geom.Ellipse2D.Float(MARGIN, MARGIN, CERCLE_WIDTH, CERCLE_WIDTH);
arrow = new Polygon(new int[]{0, MIN_SIZE / 2, MIN_SIZE / 4 }, new int[]{MIN_SIZE / 2 , MIN_SIZE / 2 , 0}, 3);
final int centerX = MARGIN+CERCLE_WIDTH/2;
resetShape = new java.awt.geom.Ellipse2D.Float(centerX-10,centerX-10,20,20);
}
@Override
protected void paintComponent(final Graphics g) {
super.paintComponent(g);
final Graphics2D g2d = (Graphics2D) g;
final Rectangle clip = g2d.getClipBounds();
if(minimized && !(clip.intersects(0, 0, MIN_SIZE+1,MIN_SIZE+1))) return;
if(!clip.intersects(0,0,MARGIN+CERCLE_WIDTH,CERCLE_WIDTH + 2*MARGIN+5)) return;
if(mustUpdate){
final Graphics2D g2 = buffer.createGraphics();
g2.setStroke(new BasicStroke(1));
g2.setFont(g2d.getFont().deriveFont(Font.BOLD));
g2.setBackground(new Color(0f,0f,0f,0f));
g2.clearRect(0, 0, buffer.getWidth(), buffer.getHeight());
g2.setRenderingHints(g2d.getRenderingHints());
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//draw the rotation cercle ---------------------------------------------
g2.rotate(rotation, MARGIN + CERCLE_WIDTH / 2, MARGIN + CERCLE_WIDTH / 2);
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f));
final Point2D center = new Point2D.Float(MARGIN + CERCLE_WIDTH / 2, MARGIN + CERCLE_WIDTH / 2);
final float radius = (float) CERCLE_WIDTH/2 ;
final float[] dist = {0.2f, 1f};
final Color[] colors = {teinteLight, base};
final RadialGradientPaint paint = new RadialGradientPaint(center, radius, dist, colors);
g2.setPaint(paint);
g2.fillOval(MARGIN, MARGIN, CERCLE_WIDTH, CERCLE_WIDTH);
g2.setPaint(teinteDark);
g2.drawOval(MARGIN, MARGIN, CERCLE_WIDTH, CERCLE_WIDTH);
final FontMetrics metrics = g2.getFontMetrics(g2.getFont());
final int posX = MARGIN + CERCLE_WIDTH / 2 ;
final int posY = MARGIN + 15 ;
final Point rotCenter = new Point(MARGIN + CERCLE_WIDTH / 2,MARGIN + CERCLE_WIDTH / 2);
final Shape line = new Line2D.Float(MARGIN+CERCLE_WIDTH/2, MARGIN+1, MARGIN+CERCLE_WIDTH/2, MARGIN + 5);
final int small = 2;
g2.setColor(north);
g2.draw(line);
String text = "N";
g2.drawString(text, posX - metrics.stringWidth(text)/2, posY);
g2.rotate(Math.PI/4, rotCenter.x, rotCenter.y);
g2.setColor(text2);
g2.draw(line);
text = "ne";
g2.drawString(text, posX - metrics.stringWidth(text)/2, posY+small);
g2.rotate(Math.PI/4, rotCenter.x, rotCenter.y);
g2.setColor(text1);
text = "E";
g2.drawString(text, posX - metrics.stringWidth(text)/2, posY);
g2.rotate(Math.PI/4, rotCenter.x, rotCenter.y);
g2.setColor(text2);
g2.draw(line);
text = "se";
g2.drawString(text, posX - metrics.stringWidth(text)/2, posY+small);
g2.rotate(Math.PI/4, rotCenter.x, rotCenter.y);
g2.setColor(text1);
text = "S";
g2.drawString(text, posX - metrics.stringWidth(text)/2, posY);
g2.rotate(Math.PI/4, rotCenter.x, rotCenter.y);
g2.setColor(text2);
g2.draw(line);
text = "sw";
g2.drawString(text, posX - metrics.stringWidth(text)/2, posY+small);
g2.rotate(Math.PI/4, rotCenter.x, rotCenter.y);
g2.setColor(text1);
text = "W";
g2.drawString(text, posX - metrics.stringWidth(text)/2, posY);
g2.rotate(Math.PI/4, rotCenter.x, rotCenter.y);
g2.setColor(text2);
g2.draw(line);
text = "nw";
g2.drawString(text, posX - metrics.stringWidth(text)/2, posY +small);
g2.rotate(Math.PI/4, rotCenter.x, rotCenter.y);
g2.rotate(-rotation, rotCenter.x, rotCenter.y);
//draw inner buttons -----------------------------------------------
final Shape oldclip = g2.getClip();
final Area area = new Area(innerCercle);
area.subtract(new Area(resetShape));
g2.setClip(area);
final int height = (int) ((CERCLE_WIDTH - 4 * MARGIN) / 3f);
final int width = (CERCLE_WIDTH - 4 * MARGIN);
g2.setPaint((overButton==0) ? teinteDark : trans);
g2.fillRect(3*MARGIN, 3*MARGIN, width, height);
g2.setPaint((overButton==3) ? teinteDark : trans);
g2.fillRect(3*MARGIN, 3*MARGIN+height, width/2, height);
g2.setPaint((overButton==1) ? teinteDark : trans);
g2.fillRect(3*MARGIN+width/2, 3*MARGIN+height, width/2, height);
g2.setPaint((overButton==2) ? teinteDark : trans);
g2.fillRect(3*MARGIN, 3*MARGIN+2*height, width, height);
g2.setColor(teinteDark);
if(inBorder){
g2.drawRect(3*MARGIN, 3*MARGIN, width, height);
g2.drawRect(3*MARGIN, 3*MARGIN+height, width/2, height);
g2.drawRect(3*MARGIN+width/2, 3*MARGIN+height, width/2, height);
g2.drawRect(3*MARGIN, 3*MARGIN+2*height, width, height);
}
//draw arrows
final int topX = 3*MARGIN + width/2-arrow.getBounds().width/2;
final int topY = 3*MARGIN + height/6 +2;
g2.translate(topX,topY);
g2.setColor(text2);
g2.fill(arrow);
g2.translate( -topX, -topY);
g2.rotate(Math.PI/2, rotCenter.x, rotCenter.y);
g2.translate(topX, topY);
g2.setColor(text2);
g2.fill(arrow);
g2.translate( -topX, -topY);
g2.rotate(Math.PI/2, rotCenter.x, rotCenter.y);
g2.translate(topX, topY);
g2.setColor(text2);
g2.fill(arrow);
g2.translate( -topX, -topY);
g2.rotate(Math.PI/2, rotCenter.x, rotCenter.y);
g2.translate(topX, topY);
g2.setColor(text2);
g2.fill(arrow);
g2.translate( -topX, -topY);
g2.rotate(Math.PI/2, rotCenter.x, rotCenter.y);
g2.setClip(oldclip);
g2.setColor(teinteDark);
g2.setStroke(new BasicStroke(2));
if(inBorder){
g2.drawOval(3 * MARGIN, 3 * MARGIN, CERCLE_WIDTH - 4 * MARGIN -1, CERCLE_WIDTH - 4 * MARGIN -1);
}
g2.setStroke(new BasicStroke(1));
//draw reset button ------------------------------------------------
g2.setPaint((overButton==4) ? teinteDark : trans);
g2.fill(resetShape);
if(inBorder){
g2.setPaint(teinteDark);
g2.draw(resetShape);
}
final int centerX = MARGIN+CERCLE_WIDTH/2;
g2.setPaint(text1);
g2.drawString("R", centerX - metrics.stringWidth("R")/2+1, centerX + metrics.getAscent()/2-1);
mustUpdate = false;
}
g2d.drawImage(buffer, 0, 0, this);
g2d.dispose();
}
private void setRotation(final double r){
rotation = r;
mustUpdate = true;
repaint(MARGIN,MARGIN,CERCLE_WIDTH,CERCLE_WIDTH);
}
private double getRotation(){
return rotation;
}
private void moveUp(){
if(map!=null){
try {
map.getCanvas().translateDisplay(0, getHeight() / 10);
} catch (NoninvertibleTransformException ex) {
LOGGER.log(Level.WARNING, null, ex);
}
}
}
private void moveDown(){
if(map!=null){
try {
map.getCanvas().translateDisplay(0, -getHeight() / 10);
} catch (NoninvertibleTransformException ex) {
LOGGER.log(Level.WARNING, null, ex);
}
}
}
private void moveLeft(){
if(map!=null){
try {
map.getCanvas().translateDisplay(getWidth() / 10, 0);
} catch (NoninvertibleTransformException ex) {
LOGGER.log(Level.WARNING, null, ex);
}
}
}
private void moveRight(){
if(map!=null){
try {
map.getCanvas().translateDisplay(-getWidth() / 10, 0);
} catch (NoninvertibleTransformException ex) {
LOGGER.log(Level.WARNING, null, ex);
}
}
}
private void mapRotate(final double d){
if (map != null) {
try {
map.getCanvas().setRotation(d);
} catch (NoninvertibleTransformException ex) {
LOGGER.log(Level.WARNING, null, ex);
}
}
}
private double calculateAngle(final int mouseX, final int mouseY){
final Point pa = new Point( (MARGIN + CERCLE_WIDTH / 2) ,0);
final Point pb = new Point( (MARGIN + CERCLE_WIDTH / 2) , (MARGIN + CERCLE_WIDTH / 2) );
final Point pc = new Point(mouseX,mouseY);
final double a = Math.pow( Math.pow( pc.x - pb.x , 2) + Math.pow( pc.y - pb.y , 2) ,0.5d);
final double b = Math.pow( Math.pow( pa.x - pc.x , 2) + Math.pow( pa.y - pc.y , 2) ,0.5d);
final double c = Math.pow( Math.pow( pa.x - pb.x , 2) + Math.pow( pa.y - pb.y , 2) ,0.5d);
// double angleA = Math.acos( ( Math.pow(b, 2) + Math.pow(c, 2) - Math.pow(a, 2) )/(2*b*c) );
double angleB = Math.acos( ( Math.pow(a, 2) + Math.pow(c, 2) - Math.pow(b, 2) )/(2*a*c) );
// double angleC = Math.acos( ( Math.pow(a, 2) + Math.pow(b, 2) - Math.pow(c, 2) )/(2*a*b) );
if(mouseX < (MARGIN + CERCLE_WIDTH / 2) ){
angleB = 2* Math.PI - angleB;
}
return angleB;
}
@Override
public boolean contains(final int x, final int y) {
return outerCercle.contains(x,y);
}
private final MouseListener mouseListener = new MouseListener() {
@Override
public void mouseClicked(final MouseEvent e) {
final Point mouse = e.getPoint();
if(resetShape.contains(mouse)){
mapRotate(0);
}
}
@Override
public void mousePressed(final MouseEvent e) {
final Point mouse = e.getPoint();
if(resetShape.contains(mouse)) return;
final int center = (MARGIN + CERCLE_WIDTH / 2);
final double tx = mouse.x - center ;
final double ty = mouse.y - center ;
final double distance = Math.hypot(tx, ty);
//draging the north
if(distance >= (CERCLE_WIDTH/2 - 2*MARGIN) &&
distance <= (CERCLE_WIDTH/2)){
actionFlag = 1;
mapRotate(calculateAngle(mouse.x, mouse.y));
}
//click on a central button
if(distance < (CERCLE_WIDTH/2 - 2*MARGIN)){
actionFlag = -1;
final int height = (int) ((CERCLE_WIDTH - 4 * MARGIN) / 3f);
if(mouse.y < 3*MARGIN + height){
moveUp();
}else if(mouse.y > 3*MARGIN + 2*height){
moveDown();
}else if(mouse.x < center){
moveLeft();
}else{
moveRight();
}
}
}
@Override
public void mouseReleased(MouseEvent e) {
actionFlag = -1;
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
if(overButton !=-1){
overButton = -1;
mustUpdate = true;
repaint(innerCercle.getBounds());
}
}
};
private final MouseMotionListener mouseMotionListener = new MouseMotionListener() {
@Override
public void mouseDragged(final MouseEvent e) {
if (actionFlag == 1) {
mapRotate(calculateAngle(e.getX(),e.getY()));
}
}
@Override
public void mouseMoved(final MouseEvent e) {
final Point mouse = e.getPoint();
//we repaint inner buttons
if(innerCercle.contains(mouse) ){
final int center = (MARGIN + CERCLE_WIDTH / 2);
final int oldOver = overButton;
final int height = (int) ((CERCLE_WIDTH - 4 * MARGIN) / 3f);
if(resetShape.contains(mouse)){
overButton = 4;
}else if (mouse.y < 3 * MARGIN + height) {
overButton = 0;
} else if (mouse.y > 3 * MARGIN + 2 * height) {
overButton = 2;
} else if (mouse.x < center) {
overButton = 3;
} else {
overButton = 1;
}
if(oldOver != overButton){
mustUpdate = true;
repaint(innerCercle.getBounds());
}
}else if(overButton != -1){
overButton = -1;
mustUpdate = true;
repaint(innerCercle.getBounds());
}
}
};
private final PropertyChangeListener propertyListener = new PropertyChangeListener() {
@Override
public void propertyChange(final PropertyChangeEvent arg0) {
if(AbstractCanvas2D.TRANSFORM_KEY.equals(arg0.getPropertyName())){
final double rotation = map.getCanvas().getRotation();
if(rotation != getRotation()){
setRotation(rotation);
}
}
}
};
@Override
public Dimension getPreferredSize() {
return new Dimension(buffer.getWidth(),buffer.getHeight());
}
@Override
public Dimension getSize() {
return getPreferredSize();
}
@Override
public Dimension getSize(final Dimension rv) {
if(rv != null){
rv.height = buffer.getHeight();
rv.width = buffer.getWidth();
return rv;
}else{
return getSize();
}
}
@Override
public void refresh() {
}
@Override
public void dispose() {
removeMouseListener(mouseListener);
removeMouseMotionListener(mouseMotionListener);
}
@Override
public void setMap2D(final JMap2D map) {
if(this.map != null){
this.map.getCanvas().removePropertyChangeListener(propertyListener);
}
this.map = map;
this.map.getCanvas().addPropertyChangeListener(propertyListener);
}
@Override
public JMap2D getMap2D() {
return map;
}
@Override
public JComponent getComponent() {
return this;
}
}