/*
* org.openmicroscopy.shoola.agents.imviewer.browser.BrowserUI
*
*------------------------------------------------------------------------------
* Copyright (C) 2006-2013 University of Dundee. All rights reserved.
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*------------------------------------------------------------------------------
*/
package org.openmicroscopy.shoola.agents.imviewer.browser;
//Java imports
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import org.openmicroscopy.shoola.agents.imviewer.view.ImViewer;
/**
* Hosts the UI components displaying the rendered image.
* Note that the layout manager of the viewport is set to <code>null</code>.
*
* @author Jean-Marie Burel
* <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
* @author Andrea Falconi
* <a href="mailto:a.falconi@dundee.ac.uk">a.falconi@dundee.ac.uk</a>
* @author Donald MacDonald
* <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a>
* @version 3.0
* <small>
* (<b>Internal version:</b> $Revision: $ $Date: $)
* </small>
* @since OME2.2
*/
class BrowserUI
extends JScrollPane
implements AdjustmentListener
{
/**
* The Layered pane hosting the {@link BrowserCanvas} and any other
* UI components added on top of it.
*/
private JLayeredPane layeredPane;
/** The canvas hosting the image. */
private JComponent canvas;
/** Reference to the Model. */
private BrowserModel model;
/** Reference to the Control. */
private BrowserControl controller;
/** Listens to the mouse moves on the Image canvas. */
private ImageCanvasListener canvasListener;
/** Components related to the view while settings the bounds. */
private Map<Integer, JComponent> siblings;
/** Flag indicating if the experimenter uses the scrollbars. */
private boolean adjusting;
/** The bird eye view.*/
private BirdEyeViewComponent birdEyeView;
/** The component hosting the bird eye view.*/
private JPanel glass;
/** Sets the location of the bird eye view.*/
private void setBirdEyeViewLocation()
{
if (birdEyeView == null) return;
Rectangle r = getVisibleRectangle();
Point p = new Point(0, 0);
p = SwingUtilities.convertPoint(getViewport(), p, glass);
switch (birdEyeView.getLocationIndex()) {
case ImageCanvas.BOTTOM_RIGHT:
Dimension d = birdEyeView.getSize();
p = new Point(p.x+r.width-d.width, p.y+r.height-d.height);
birdEyeView.setLocation(p);
break;
case ImageCanvas.TOP_LEFT:
default:
birdEyeView.setLocation(p);
}
}
/**
* Converts the bird eye location to screen location.
*
* @param region The region to convert.
* @return See above.
*/
private Rectangle convertFromSelection(Rectangle region)
{
Dimension d = birdEyeView.getImageSize();
Rectangle rl = canvas.getBounds();
int sizeX = rl.width;
int sizeY = rl.height;
double vx = sizeX/d.width;
double vy = sizeY/d.height;
int x = (int) (vx*region.x);
int y = (int) (vy*region.y);
int w = (int) (vx*region.width);
int h = (int) (vy*region.height);
return new Rectangle(x, y, w, h);
}
/**
* Displays the region of the image selected using the bird eye view.
*
* @param region See above.
* @param load Pass <code>true</code> to load the tiles, <code>false</code>
* otherwise.
*/
private void displaySelectedRegion(Rectangle region, boolean load)
{
if (region == null) return;
Rectangle r = convertFromSelection(region);
getViewport().setViewPosition(new Point(-1, -1));
scrollTo(r, false);
if (load) model.loadTiles(null);
}
/** Sets the location of the region.*/
private void setSelectionRegion()
{
if (birdEyeView == null) return;
Dimension d = birdEyeView.getImageSize();
if (d.width == 0 || d.height == 0) return;
Rectangle rect = getVisibleRectangle();
int sizeX = model.getTiledImageSizeX();
int sizeY = model.getTiledImageSizeY();
int rx = sizeX/d.width;
int ry = sizeY/d.height;
if (rx == 0) rx = 1;
if (ry == 0) ry = 1;
int w = (int) (rect.width/rx);
int h = (int) (rect.height/ry);
int x = (int) (rect.x/rx);
int y = (int) (rect.y/ry);
if (x < 0) x = 0;
if (y < 0) y = 0;
birdEyeView.setSelection(x, y, w, h);
}
/** Centers the image.*/
private void center()
{
Rectangle r = getVisibleRectangle();
Dimension d = layeredPane.getPreferredSize();
int xLoc = ((r.width-d.width)/2);
int yLoc = ((r.height-d.height)/2);
JScrollBar hBar = getHorizontalScrollBar();
JScrollBar vBar = getVerticalScrollBar();
if (hBar.isVisible()) xLoc = layeredPane.getX();
if (vBar.isVisible()) yLoc = layeredPane.getY();
JComponent sibling = siblings.get(model.getSelectedIndex());
if (sibling != null)
sibling.setBounds(sibling.getBounds());
layeredPane.setBounds(xLoc, yLoc, d.width, d.height);
setBirdEyeViewLocation();
}
/** Initializes the components composing the display. */
private void initComponents()
{
layeredPane = new JLayeredPane();
canvas = new BrowserBICanvas(model, this);
//The image canvas is always at the bottom of the pile.
layeredPane.add(canvas, Integer.valueOf(0));
canvasListener = new ImageCanvasListener(this, model, canvas);
canvasListener.setHandleKeyDown(true);
MouseAdapter adapter = new MouseAdapter() {
/**
* Removes the adjustment listener to the scroll bars.
* @see MouseListener#mouseReleased(MouseEvent)
*/
public void mouseReleased(MouseEvent e) {
installScrollbarListener(false);
}
/**
* Attaches an adjustment listener to the scroll bars.
* @see MouseListener#mousePressed(MouseEvent)
*/
public void mousePressed(MouseEvent e) {
installScrollbarListener(true);
}
};
getVerticalScrollBar().addMouseListener(adapter);
getHorizontalScrollBar().addMouseListener(adapter);
}
/** Builds and lays out the GUI. */
private void buildGUI()
{
JViewport viewport = getViewport();
viewport.setLayout(null);
viewport.setBackground(model.getBackgroundColor());
viewport.add(layeredPane);
}
/**
* Returns <code>true</code> if the scrollbars are visible,
* <code>false</code> otherwise.
*
* @return See above.
*/
private boolean scrollbarsVisible()
{
JScrollBar hBar = getHorizontalScrollBar();
JScrollBar vBar = getVerticalScrollBar();
if (hBar.isVisible()) return true;
if (vBar.isVisible()) return true;
return false;
}
/** Creates a new instance. */
BrowserUI()
{
siblings = new HashMap<Integer, JComponent>();
}
/**
* Links this View to its Controller and Model
*
* @param controller Reference to the Control.
* Mustn't be <code>null</code>.
* @param model Reference to the Model.
* Mustn't be <code>null</code>.
*/
void initialize(BrowserControl controller, BrowserModel model)
{
if (model == null) throw new NullPointerException("No model.");
if (controller == null) throw new NullPointerException("No control.");
this.model = model;
this.controller = controller;
initComponents();
buildGUI();
}
/**
* Sets the component related to this component when the bounds of
* the view are reset.
*
* @param index The index corresponding to the passed component.
* @param sibling The value to set.
*/
void setSibling(int index, JComponent sibling)
{
siblings.put(index, sibling);
}
/**
* Adds the component to the {@link #layeredPane}. The component will
* be added to the top of the pile
*
* @param c The component to add.
* @param reset Pass <code>true</code> to re-organize the components,
* <code>false</code> otherwise.
*/
void addComponentToLayer(Component c, boolean reset)
{
Component[] components = layeredPane.getComponents();
for (int i = 0; i < components.length; i++) {
if (components[i] == c) return;
}
if (reset) {
for (int i = 0; i < components.length; i++) {
if (components[i] != canvas)
layeredPane.remove(components[i]);
}
layeredPane.add(c, Integer.valueOf(1));
for (int i = 0; i < components.length; i++) {
if (components[i] != canvas)
layeredPane.add(components[i], Integer.valueOf(1));
}
} else layeredPane.add(c, Integer.valueOf(1));
}
/**
* Initializes or recycles the bird eye view and add it to the
* display.
*
* @param image The image to display
*/
void setBirdEyeView(BufferedImage image)
{
boolean init = false;
if (birdEyeView == null) {
birdEyeView = new BirdEyeViewComponent(
ImageCanvas.BOTTOM_RIGHT);
birdEyeView.addPropertyChangeListener(new PropertyChangeListener() {
/**
* Listen to the property indicating to display a new location.
* @see PropertyChangeListener#propertyChange(PropertyChangeEvent)
*/
public void propertyChange(PropertyChangeEvent evt)
{
String name = evt.getPropertyName();
if (BirdEyeViewComponent.DISPLAY_REGION_PROPERTY.equals(
name)) {
displaySelectedRegion((Rectangle) evt.getNewValue(),
true);
} else if (
BirdEyeViewComponent.FULL_DISPLAY_PROPERTY.equals(
name)) {
setBirdEyeViewLocation();
}
}
});
birdEyeView.setup(0, 0);
JFrame frame = model.getParentModel().getUI();
glass = (JPanel) frame.getGlassPane();
glass.setLayout(null);
glass.add(birdEyeView);
glass.setVisible(true);
setBirdEyeViewLocation();
init = true;
}
birdEyeView.setImage(image);
if (init && image != null) {
int width = image.getWidth();
int height = image.getHeight();
Rectangle r = getVisibleRectangle();
int sizeX = model.getTiledImageSizeX();
int sizeY = model.getTiledImageSizeY();
int rx = sizeX/width;
int ry = sizeY/height;
if (rx == 0) rx = 1;
if (ry == 0) ry = 1;
int w = (int) (r.width/rx);
int h = (int) (r.height/ry);
int x = (int) (width-w)/2;
int y = (int) (height-h)/2;
birdEyeView.setSelection(x, y, w, h);
r = birdEyeView.getSelectionRegion();
displaySelectedRegion(r, false);
}
}
/**
* Removes the component from the {@link #layeredPane}.
*
* @param c The component to remove.
*/
void removeComponentFromLayer(JComponent c)
{
layeredPane.remove(c);
}
/**
* Creates the displayed image and paints it.
* This method should be called straight after setting the
* rendered image.
*/
void paintMainImage()
{
if (model.getRenderedImage() == null) return;
model.createDisplayedImage();
BufferedImage img = model.getDisplayedImage();
if (img == null) return;
canvasListener.setAreaSize(img.getWidth(), img.getHeight());
canvas.repaint();
}
/** Displays the zoomed image. */
void zoomImage()
{
adjusting = false;
if (model.getRenderedImage() == null) return;
model.createDisplayedImage();
BufferedImage img = model.getDisplayedImage();
if (img == null) return;
setComponentsSize(img.getWidth(), img.getHeight());
canvasListener.setAreaSize(img.getWidth(), img.getHeight());
getViewport().setViewPosition(new Point(-1, -1));
canvas.repaint();
setBounds(getBounds());
getViewport().setViewPosition(new Point(-1, -1));
canvas.repaint();
setBounds(getBounds());
}
/**
* Sets the size of the components because a layeredPane doesn't have a
* layout manager.
*
* @param w The width to set.
* @param h The height to set.
*/
void setComponentsSize(int w, int h)
{
Dimension d = new Dimension(w, h);
layeredPane.setPreferredSize(d);
layeredPane.setSize(d);
canvas.setPreferredSize(d);
canvas.setSize(d);
if (model.isBigImage()) {
Rectangle r = getVisibleRectangle();
d = layeredPane.getPreferredSize();
if (d.width < r.width && d.height < r.height) {
center();
}
}
}
/**
* Returns the current size of the viewport.
*
* @return see above.
*/
Dimension getViewportSize() { return getViewport().getSize(); }
/**
* Installs or removes listener to (resp. from) scroll bars.
*
* @param add Passes <code>true</code> to install,
* <code>false</code> to remove.
*/
private void installScrollbarListener(boolean add)
{
if (add) {
getHorizontalScrollBar().addAdjustmentListener(this);
getVerticalScrollBar().addAdjustmentListener(this);
} else {
getHorizontalScrollBar().removeAdjustmentListener(this);
getVerticalScrollBar().removeAdjustmentListener(this);
}
}
/**
* Pans to the new location.
*
* @param x The X-coordinate of the mouse dragged minus mouse pressed.
* @param y The Y-coordinate of the mouse dragged minus mouse pressed.
* @param load Passed <code>true</code> to load, <code>false</code>
* otherwise.
*/
void pan(int x, int y, boolean load)
{
Rectangle r = getVisibleRectangle();
if (r.contains(canvas.getBounds())) return;
int vx = r.x;
int vy = r.y;
if (x < 0) vx += -x;
if (x > 0) vx -= x;
if (y < 0) vy += -y;
if (y > 0) vy -= y;
getViewport().setViewPosition(new Point(vx, vy));
setSelectionRegion();
setBirdEyeViewLocation();
if (load) model.loadTiles(getVisibleRectangle());
}
/**
* Sets the selected region.
*
* @param region The selected region.
*/
void setSelectedRegion(Rectangle region)
{
scrollTo(region, false);
setSelectionRegion();
setBirdEyeViewLocation();
model.loadTiles(getVisibleRectangle());
}
/**
* Scrolls to the location.
*
* @param bounds The bounds of the node.
* @param blockIncrement Pass <code>true</code> to consider block
* increment, <code>false</code> otherwise.
*
*/
void scrollTo(Rectangle bounds, boolean blockIncrement)
{
Rectangle viewRect = getVisibleRectangle();
JScrollBar hBar = getHorizontalScrollBar();
JScrollBar vBar = getVerticalScrollBar();
int x = 0;
int y = 0;
if (!viewRect.contains(bounds)) {
int deltaX = viewRect.x-bounds.x;
int deltaY = viewRect.y-bounds.y;
if (deltaX < 0 && blockIncrement)
x = hBar.getValue()+hBar.getBlockIncrement();
else {
int w = viewRect.width-bounds.width;
if (w < 0) w = -w;
x = bounds.x-w/2;
}
if (deltaY < 0 && blockIncrement)
y = vBar.getValue()+vBar.getBlockIncrement();
else {
int h = viewRect.height-bounds.height;
if (h < 0) h = -h;
y = bounds.y-h/2;
}
} else {
//lens not centered
if (blockIncrement) return;
int w = viewRect.width-bounds.width;
if (w < 0) w = -w;
x = bounds.x-w/2;
int h = viewRect.height-bounds.height;
if (h < 0) h = -h;
y = bounds.y-h/2;
}
vBar.setValue(y);
hBar.setValue(x);
setBirdEyeViewLocation();
}
/**
* Sets the value of the horizontal and vertical scrollBars.
*
* @param vValue The value to set for the vertical scrollBar.
* @param hValue The value to set for the horizontal scrollBar.
*/
void scrollTo(int vValue, int hValue)
{
JScrollBar vBar = getVerticalScrollBar();
JScrollBar hBar = getHorizontalScrollBar();
hBar.setValue(hBar.getValue()+hValue);
vBar.setValue(vBar.getValue()+vValue);
setBirdEyeViewLocation();
}
/** Clears the grid images. */
void clearGridImages() { model.clearGridImages(); }
/**
* Returns <code>true</code> if the user is adjusting the window,
* <code>false</code> otherwise.
*
* @return See above.
*/
boolean isAdjusting() { return adjusting; }
/** Locates the scroll bars. */
void locateScrollBars()
{
if (!scrollbarsVisible()) return;
scrollTo(getVisibleRectangle(), false);
}
/**
* Returns the location of the bird eye view.
*
* @return See above.
*/
int getBirdEyeViewLocationIndex()
{
if (birdEyeView == null) return -1;
return birdEyeView.getLocationIndex();
}
/**
* Sets the adjusting value to <code>false</code>.
* and sets the bird eye view location.
*/
void onComponentResized()
{
adjusting = false;
center();
setSelectionRegion();
}
/**
* Returns the rectangle used to load the tiles.
*
* @return See above.
*/
Rectangle getVisibleRectangle()
{
return getViewport().getViewRect();
}
/**
* Reacts to {@link ImViewer} change events.
*
* @param b Pass <code>true</code> to enable the UI components,
* <code>false</code> otherwise.
*/
void onStateChange(boolean b)
{
JScrollBar bar = getHorizontalScrollBar();
if (bar.isVisible()) bar.setEnabled(b);
bar = getVerticalScrollBar();
if (bar.isVisible()) bar.setEnabled(b);
if (birdEyeView != null) birdEyeView.installListeners(b);
canvasListener.installListeners(b);
}
/**
* Sets the location of the selection region when the user zooms in or out.
*
* @param rx The ratio along the X-axis.
* @param ry The ratio along the Y-axis.
*/
void setViewLocation(double rx, double ry)
{
Dimension d = birdEyeView.getImageSize();
Rectangle r = birdEyeView.getSelectionRegion();
double cx = r.getCenterX();
double cy = r.getCenterY();
Rectangle rect = getVisibleRectangle();
int sizeX = model.getTiledImageSizeX();
int sizeY = model.getTiledImageSizeY();
int rxx = sizeX/d.width;
int ryy = sizeY/d.height;
if (rxx == 0) rxx = 1;
if (ryy == 0) ryy = 1;
int w = (int) (rect.width/rxx);
int h = (int) (rect.height/ryy);
int x = (int) (cx-w/2);
int y = (int) (cy-h/2);
if (x < 0) x = 0;
if (y < 0) y = 0;
birdEyeView.setSelection(x, y, w, h);
displaySelectedRegion(birdEyeView.getSelectionRegion(), true);
}
/**
* Sets the location of the bird eye to be sure that it is always visible.
* @see AdjustmentListener#adjustmentValueChanged(AdjustmentEvent)
*/
public void adjustmentValueChanged(AdjustmentEvent e)
{
if (e.getValueIsAdjusting()) {
adjusting = true;
setBirdEyeViewLocation();
setSelectionRegion();
return;
}
adjusting = false;
setBirdEyeViewLocation();
setSelectionRegion();
model.loadTiles(getVisibleRectangle());
}
/**
* Overridden to center the image.
* @see JComponent#setBounds(Rectangle)
*/
public void setBounds(Rectangle r)
{
setBounds(r.x, r.y, r.width, r.height);
}
/**
* Overridden to center the image.
* @see JComponent#setBounds(int, int, int, int)
*/
public void setBounds(int x, int y, int width, int height)
{
super.setBounds(x, y, width, height);
Rectangle r = getVisibleRectangle();
Dimension d = layeredPane.getPreferredSize();
if (model.isBigImage()) {
setBirdEyeViewLocation();
if (!(d.width < r.width && d.height < r.height))
return;
}
if (!scrollbarsVisible() && adjusting) adjusting = false;
JScrollBar hBar = getHorizontalScrollBar();
JScrollBar vBar = getVerticalScrollBar();
if (!(hBar.isVisible() && vBar.isVisible())) center();
}
}