/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2010, 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.navigator;
import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import static java.awt.event.KeyEvent.*;
import java.awt.event.KeyListener;
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.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import static javax.swing.SwingConstants.*;
import org.apache.sis.measure.NumberRange;
import org.geotoolkit.util.collection.NotifiedCheckedList;
/**
*
* @author Johann Sorel (Geomatys)
* @module
*/
public class JNavigator extends JPanel implements
MouseListener, MouseMotionListener, MouseWheelListener, KeyListener {
private final NavigatorModel model = new DoubleNavigatorModel(null);
private NavigatorRenderer renderer;
private final JComponent graduation = new JComponent(){};
private final JPanel bandsPan = new JPanel();
private final List<JNavigatorBand> bands = new NotifiedCheckedList<JNavigatorBand>(JNavigatorBand.class){
@Override
protected void notifyAdd(JNavigatorBand band, int index) {
band.setModel(getModel());
band.setNavigator(JNavigator.this);
band.addMouseListener(JNavigator.this);
band.addMouseMotionListener(JNavigator.this);
band.addMouseWheelListener(JNavigator.this);
band.addKeyListener(JNavigator.this);
updateDisplay();
}
@Override
protected void notifyAdd(Collection<? extends JNavigatorBand> items, NumberRange<Integer> range) {
for(JNavigatorBand band : items){
band.setModel(getModel());
band.setNavigator(JNavigator.this);
band.addMouseListener(JNavigator.this);
band.addMouseMotionListener(JNavigator.this);
band.addMouseWheelListener(JNavigator.this);
band.addKeyListener(JNavigator.this);
}
updateDisplay();
}
@Override
protected void notifyRemove(JNavigatorBand band, int index) {
band.removeMouseListener(JNavigator.this);
band.removeMouseMotionListener(JNavigator.this);
band.removeMouseWheelListener(JNavigator.this);
band.removeKeyListener(JNavigator.this);
updateDisplay();
}
@Override
protected void notifyRemove(Collection<? extends JNavigatorBand> items, NumberRange<Integer> range) {
for(JNavigatorBand band : items){
band.removeMouseListener(JNavigator.this);
band.removeMouseMotionListener(JNavigator.this);
band.removeMouseWheelListener(JNavigator.this);
band.removeKeyListener(JNavigator.this);
}
updateDisplay();
}
@Override
protected void notifyChange(JNavigatorBand oldItem, JNavigatorBand newItem, int index) {
if(oldItem != null){
oldItem.removeMouseListener(JNavigator.this);
oldItem.removeMouseMotionListener(JNavigator.this);
oldItem.removeMouseWheelListener(JNavigator.this);
oldItem.removeKeyListener(JNavigator.this);
}
if(newItem != null){
newItem.setModel(getModel());
newItem.setNavigator(JNavigator.this);
newItem.addMouseListener(JNavigator.this);
newItem.addMouseMotionListener(JNavigator.this);
newItem.addMouseWheelListener(JNavigator.this);
newItem.addKeyListener(JNavigator.this);
}
updateDisplay();
}
};
private int orientation = SwingConstants.SOUTH;
private final PropertyChangeListener listener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent pce) {
revalidate();
repaint();
}
};
public JNavigator() {
super(new BorderLayout(0,0));
renderer = new DoubleRenderer();
bandsPan.setOpaque(false);
bandsPan.setInheritsPopupMenu(true);
graduation.setInheritsPopupMenu(true);
final BoxLayout bl = new BoxLayout(bandsPan, BoxLayout.Y_AXIS);
bandsPan.setLayout(bl);
model.addPropertyChangeListener(listener);
this.graduation.setOpaque(false);
add(BorderLayout.SOUTH,graduation);
final JScrollPane scroller = new JScrollPane(bandsPan);
scroller.setOpaque(false);
scroller.setInheritsPopupMenu(true);
scroller.setBorder(null);
scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scroller.getViewport().setBorder(null);
scroller.getViewport().setOpaque(false);
scroller.getViewport().setInheritsPopupMenu(true);
add(BorderLayout.CENTER,scroller);
addMouseListener(this);
addMouseMotionListener(this);
addMouseWheelListener(this);
addKeyListener(this);
graduation.addMouseListener(this);
graduation.addMouseMotionListener(this);
graduation.addMouseWheelListener(this);
graduation.addKeyListener(this);
bandsPan.addMouseListener(this);
bandsPan.addMouseMotionListener(this);
bandsPan.addMouseWheelListener(this);
bandsPan.addKeyListener(this);
}
@Override
public void setComponentPopupMenu(final JPopupMenu popup) {
super.setComponentPopupMenu(popup);
graduation.setComponentPopupMenu(popup);
}
private void updateDisplay(){
bandsPan.removeAll();
for(JNavigatorBand band : bands){
bandsPan.add(band);
}
bandsPan.revalidate();
bandsPan.repaint();
}
/**
* Nodifiable list of bands to display.
*/
public List<JNavigatorBand> getBands(){
return bands;
}
public NavigatorModel getModel() {
return model;
}
public NavigatorRenderer getModelRenderer() {
return renderer;
}
public void setModelRenderer(final NavigatorRenderer renderer) {
this.renderer = renderer;
if(this.renderer != null){
graduation.setPreferredSize(new Dimension(this.renderer.getGraduationHeight(), this.renderer.getGraduationHeight()));
revalidate();
repaint();
}
}
public void setOrientation(final int orientation) {
if(this.orientation != orientation){
this.orientation = orientation;
//change the order
removeAll();
bandsPan.removeAll();
if(orientation == NORTH){
add(BorderLayout.NORTH,graduation);
bandsPan.setLayout(new GridLayout(0, 1));
}else if(orientation == SOUTH){
add(BorderLayout.SOUTH,graduation);
bandsPan.setLayout(new GridLayout(0, 1));
}else if(orientation == EAST){
add(BorderLayout.EAST,graduation);
bandsPan.setLayout(new GridLayout(1, 0));
}else if(orientation == WEST){
add(BorderLayout.WEST,graduation);
bandsPan.setLayout(new GridLayout(1, 0));
}else{
throw new IllegalArgumentException("Orientation doesnt have a valid value :" + orientation);
}
add(BorderLayout.CENTER, bandsPan);
for(JNavigatorBand band : bands){
bandsPan.add(band);
}
bandsPan.revalidate();
revalidate();
repaint();
}
}
public int getOrientation() {
return orientation;
}
@Override
protected void paintComponent(final Graphics grphcs) {
super.paintComponent(grphcs);
final Graphics2D g = (Graphics2D) grphcs;
if(renderer != null){
renderer.render(this, g, new Rectangle(getWidth(), getHeight()));
}
}
////////////////////////////////////////////////////////////////////////////
// navigation events ///////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
private int lastMouseX = 0;
private int lastMouseY = 0;
private int newMouseX = 0;
private int newMouseY = 0;
private boolean flagMove = false;
@Override
public void mouseClicked(final MouseEvent e) {
}
@Override
public void mousePressed(final MouseEvent e) {
if(e.isConsumed()) return;
flagMove = (e.getButton() == MouseEvent.BUTTON1);
newMouseX = e.getX();
newMouseY = e.getY();
lastMouseX = newMouseX;
lastMouseY = newMouseY;
}
@Override
public void mouseReleased(final MouseEvent e) {
if(e.isConsumed()) return;
flagMove = false;
}
@Override
public void mouseEntered(final MouseEvent e) {
requestFocus();
}
@Override
public void mouseExited(final MouseEvent e) {
}
@Override
public void mouseDragged(final MouseEvent e) {
if(e.isConsumed()) return;
if(!flagMove) return;
newMouseX = e.getX();
newMouseY = e.getY();
final double tr;
switch(orientation){
case NORTH : tr = lastMouseX-newMouseX; break;
case SOUTH : tr = lastMouseX-newMouseX; break;
case EAST : tr = lastMouseY-newMouseY; break;
default : tr = lastMouseY-newMouseY; break;
}
final double scale = getModel().getScale();
getModel().translate(-tr/scale);
lastMouseX = newMouseX;
lastMouseY = newMouseY;
}
@Override
public void mouseMoved(final MouseEvent e) {
setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
setToolTipText(null);
}
@Override
public void mouseWheelMoved(final MouseWheelEvent e) {
final int x = getPosition(e);
if (e.getWheelRotation() > 0) {
getModel().scale(1.1d, x);
} else {
getModel().scale(0.9d, x);
}
}
@Override
public void keyTyped(final KeyEvent e) {
}
@Override
public void keyPressed(final KeyEvent e) {
final int code = e.getKeyCode();
final int x = getPosition(null);
final double scale = getModel().getScale();
final int speed = 3;
switch(orientation){
case NORTH :
switch(code){
case VK_RIGHT : getModel().translate(-speed/scale);break;
case VK_LEFT : getModel().translate(speed/scale);break;
case VK_UP : getModel().scale(0.9d, x);break;
case VK_DOWN : getModel().scale(1.1d, x);break;
} break;
case SOUTH :
switch(code){
case VK_RIGHT : getModel().translate(-speed/scale);break;
case VK_LEFT : getModel().translate(speed/scale);break;
case VK_UP : getModel().scale(1.1d, x);break;
case VK_DOWN : getModel().scale(0.9d, x);break;
} break;
case EAST :
switch(code){
case VK_RIGHT : getModel().scale(0.9d, x);break;
case VK_LEFT : getModel().scale(1.1d, x);break;
case VK_UP : getModel().translate(-speed/scale);break;
case VK_DOWN : getModel().translate(speed/scale);break;
} break;
case WEST :
switch(code){
case VK_RIGHT : getModel().scale(1.1d, x);break;
case VK_LEFT : getModel().scale(0.9d, x);break;
case VK_UP : getModel().translate(-speed/scale);break;
case VK_DOWN : getModel().translate(speed/scale);break;
} break;
}
}
@Override
public void keyReleased(final KeyEvent e) {
}
/**
* used to define the scaling center.
*/
private int getPosition(final MouseEvent e){
if(e != null){
switch(orientation){
case NORTH : return e.getX();
case SOUTH : return e.getX();
case EAST : return e.getY();
default : return e.getY();
}
}else{
switch(orientation){
case NORTH : return getWidth()/2;
case SOUTH : return getWidth()/2;
case EAST : return getHeight()/2;
default : return getHeight()/2;
}
}
}
}