package ij.gui;
import java.awt.*;
import java.awt.image.*;
import java.util.Properties;
import java.awt.event.*;
import ij.*;
import ij.process.*;
import ij.io.*;
import ij.measure.*;
import ij.plugin.frame.*;
import ij.macro.Interpreter;
/** A frame for displaying images. */
public class ImageWindow extends Frame implements FocusListener, WindowListener, WindowStateListener, MouseWheelListener {
public static final int MIN_WIDTH = 128;
public static final int MIN_HEIGHT = 32;
protected ImagePlus imp;
protected ImageJ ij;
protected ImageCanvas ic;
private double initialMagnification = 1;
private int newWidth, newHeight;
protected boolean closed;
private boolean newCanvas;
private boolean unzoomWhenMinimizing = true;
Rectangle maxWindowBounds; // largest possible window on this screen
Rectangle maxBounds; // Size of this window after it is maximized
long setMaxBoundsTime;
private static final int XINC = 8;
private static final int YINC = 12;
private static final int TEXT_GAP = 10;
private static int xbase = -1;
private static int ybase;
private static int xloc;
private static int yloc;
private static int count;
private static boolean centerOnScreen;
private static Point nextLocation;
private int textGap = centerOnScreen?0:TEXT_GAP;
/** This variable is set false if the user presses the escape key or closes the window. */
public boolean running;
/** This variable is set false if the user clicks in this
window, presses the escape key, or closes the window. */
public boolean running2;
public ImageWindow(String title) {
super(title);
}
public ImageWindow(ImagePlus imp) {
this(imp, null);
}
public ImageWindow(ImagePlus imp, ImageCanvas ic) {
super(imp.getTitle());
if (Prefs.blackCanvas && getClass().getName().equals("ij.gui.ImageWindow")) {
setForeground(Color.white);
setBackground(Color.black);
} else {
setForeground(Color.black);
if (IJ.isLinux())
setBackground(ImageJ.backgroundColor);
else
setBackground(Color.white);
}
boolean hyperstack = imp.isHyperStack();
ij = IJ.getInstance();
this.imp = imp;
if (ic==null)
{ic=new ImageCanvas(imp); newCanvas=true;}
this.ic = ic;
ImageWindow previousWindow = imp.getWindow();
setLayout(new ImageLayout(ic));
add(ic);
addFocusListener(this);
addWindowListener(this);
addWindowStateListener(this);
addKeyListener(ij);
setFocusTraversalKeysEnabled(false);
if (!(this instanceof StackWindow))
addMouseWheelListener(this);
setResizable(true);
WindowManager.addWindow(this);
imp.setWindow(this);
if (previousWindow!=null) {
if (newCanvas)
setLocationAndSize(false);
else
ic.update(previousWindow.getCanvas());
Point loc = previousWindow.getLocation();
setLocation(loc.x, loc.y);
if (!(this instanceof StackWindow)) {
pack();
show();
}
if (ic.getMagnification()!=0.0)
imp.setTitle(imp.getTitle());
boolean unlocked = imp.lockSilently();
boolean changes = imp.changes;
imp.changes = false;
previousWindow.close();
imp.changes = changes;
if (unlocked)
imp.unlock();
if (hyperstack && this.imp!=null)
this.imp.setOpenAsHyperStack(true);
WindowManager.setCurrentWindow(this);
} else {
setLocationAndSize(false);
if (ij!=null && !IJ.isMacintosh()) {
Image img = ij.getIconImage();
if (img!=null)
try {setIconImage(img);} catch (Exception e) {}
}
if (centerOnScreen) {
GUI.center(this);
centerOnScreen = false;
} else if (nextLocation!=null) {
setLocation(nextLocation);
nextLocation = null;
}
if (Interpreter.isBatchMode() || (IJ.getInstance()==null&&this instanceof HistogramWindow)) {
WindowManager.setTempCurrentImage(imp);
Interpreter.addBatchModeImage(imp);
} else
show();
}
}
private void setLocationAndSize(boolean updating) {
int width = imp.getWidth();
int height = imp.getHeight();
Rectangle maxWindow = getMaxWindow(0,0);
//if (maxWindow.x==maxWindow.width) // work around for Linux bug
// maxWindow = new Rectangle(0, maxWindow.y, maxWindow.width, maxWindow.height);
if (WindowManager.getWindowCount()<=1)
xbase = -1;
if (width>maxWindow.width/2 && xbase>maxWindow.x+5+XINC*6)
xbase = -1;
if (xbase==-1) {
count = 0;
xbase = maxWindow.x + 5;
if (width*2<=maxWindow.width)
xbase = maxWindow.x+maxWindow.width/2-width/2;
ybase = maxWindow.y;
xloc = xbase;
yloc = ybase;
}
int x = xloc;
int y = yloc;
xloc += XINC;
yloc += YINC;
count++;
if (count%6==0) {
xloc = xbase;
yloc = ybase;
}
int sliderHeight = (this instanceof StackWindow)?20:0;
int screenHeight = maxWindow.y+maxWindow.height-sliderHeight;
double mag = 1;
while (xbase+XINC*4+width*mag>maxWindow.x+maxWindow.width || ybase+height*mag>=screenHeight) {
//IJ.log(mag+" "+xbase+" "+width*mag+" "+maxWindow.width);
double mag2 = ImageCanvas.getLowerZoomLevel(mag);
if (mag2==mag) break;
mag = mag2;
}
if (mag<1.0) {
initialMagnification = mag;
ic.setDrawingSize((int)(width*mag), (int)(height*mag));
}
ic.setMagnification(mag);
if (y+height*mag>screenHeight)
y = ybase;
if (!updating) setLocation(x, y);
if (Prefs.open100Percent && ic.getMagnification()<1.0) {
while(ic.getMagnification()<1.0)
ic.zoomIn(0, 0);
setSize(Math.min(width, maxWindow.width-x), Math.min(height, screenHeight-y));
validate();
} else
pack();
}
Rectangle getMaxWindow(int xloc, int yloc) {
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
Rectangle bounds = ge.getMaximumWindowBounds();
//bounds.x=960; bounds.y=0; bounds.width=960; bounds.height=1200;
if (IJ.debugMode) IJ.log("getMaxWindow: "+bounds+" "+xloc+","+yloc);
if (xloc>bounds.x+bounds.width || yloc>bounds.y+bounds.height) {
Rectangle bounds2 = getSecondaryMonitorBounds(ge, xloc, yloc);
if (bounds2!=null) return bounds2;
}
Dimension ijSize = ij!=null?ij.getSize():new Dimension(0,0);
if (bounds.height>600) {
bounds.y += ijSize.height;
bounds.height -= ijSize.height;
}
return bounds;
}
private Rectangle getSecondaryMonitorBounds(GraphicsEnvironment ge, int xloc, int yloc) {
//IJ.log("getSecondaryMonitorBounds "+wb);
GraphicsDevice[] gs = ge.getScreenDevices();
for (int j=0; j<gs.length; j++) {
GraphicsDevice gd = gs[j];
GraphicsConfiguration[] gc = gd.getConfigurations();
for (int i=0; i<gc.length; i++) {
Rectangle bounds = gc[i].getBounds();
//IJ.log(j+" "+i+" "+bounds+" "+bounds.contains(wb.x, wb.y));
if (bounds!=null && bounds.contains(xloc, yloc))
return bounds;
}
}
return null;
}
public double getInitialMagnification() {
return initialMagnification;
}
/** Override Container getInsets() to make room for some text above the image. */
public Insets getInsets() {
Insets insets = super.getInsets();
double mag = ic.getMagnification();
int extraWidth = (int)((MIN_WIDTH - imp.getWidth()*mag)/2.0);
if (extraWidth<0) extraWidth = 0;
int extraHeight = (int)((MIN_HEIGHT - imp.getHeight()*mag)/2.0);
if (extraHeight<0) extraHeight = 0;
insets = new Insets(insets.top+textGap+extraHeight, insets.left+extraWidth, insets.bottom+extraHeight, insets.right+extraWidth);
return insets;
}
/** Draws the subtitle. */
public void drawInfo(Graphics g) {
if (textGap!=0) {
Insets insets = super.getInsets();
if (imp.isComposite()) {
CompositeImage ci = (CompositeImage)imp;
if (ci.getMode()==CompositeImage.COMPOSITE)
g.setColor(ci.getChannelColor());
}
g.drawString(createSubtitle(), insets.left+5, insets.top+TEXT_GAP);
}
}
/** Creates the subtitle. */
public String createSubtitle() {
String s="";
int nSlices = imp.getStackSize();
if (nSlices>1) {
ImageStack stack = imp.getStack();
int currentSlice = imp.getCurrentSlice();
s += currentSlice+"/"+nSlices;
String label = stack.getShortSliceLabel(currentSlice);
if (label!=null && label.length()>0) {
if (imp.isHyperStack()) label = label.replace(';', ' ');
s += " (" + label + ")";
}
if ((this instanceof StackWindow) && running2) {
return s;
}
s += "; ";
} else {
String label = (String)imp.getProperty("Label");
if (label!=null) {
int newline = label.indexOf('\n');
if (newline>0)
label = label.substring(0, newline);
int len = label.length();
if (len>4 && label.charAt(len-4)=='.' && !Character.isDigit(label.charAt(len-1)))
label = label.substring(0,len-4);
if (label.length()>60)
label = label.substring(0, 60);
s = label + "; ";
}
}
int type = imp.getType();
Calibration cal = imp.getCalibration();
if (cal.scaled()) {
s += IJ.d2s(imp.getWidth()*cal.pixelWidth,2) + "x" + IJ.d2s(imp.getHeight()*cal.pixelHeight,2)
+ " " + cal.getUnits() + " (" + imp.getWidth() + "x" + imp.getHeight() + "); ";
} else
s += imp.getWidth() + "x" + imp.getHeight() + " pixels; ";
double size = ((double)imp.getWidth()*imp.getHeight()*imp.getStackSize())/1024.0;
switch (type) {
case ImagePlus.GRAY8:
case ImagePlus.COLOR_256:
s += "8-bit";
break;
case ImagePlus.GRAY16:
s += "16-bit";
size *= 2.0;
break;
case ImagePlus.GRAY32:
s += "32-bit";
size *= 4.0;
break;
case ImagePlus.COLOR_RGB:
s += "RGB";
size *= 4.0;
break;
}
if (imp.isInvertedLut())
s += " (inverting LUT)";
String s2=null, s3=null;
if (size<1024.0)
{s2=IJ.d2s(size,0); s3="K";}
else if (size<10000.0)
{s2=IJ.d2s(size/1024.0,1); s3="MB";}
else if (size<1048576.0)
{s2=IJ.d2s(Math.round(size/1024.0),0); s3="MB";}
else
{s2=IJ.d2s(size/1048576.0,1); s3="GB";}
if (s2.endsWith(".0")) s2 = s2.substring(0, s2.length()-2);
return s+"; "+s2+s3;
}
public void paint(Graphics g) {
//if (IJ.debugMode) IJ.log("wPaint: " + imp.getTitle());
drawInfo(g);
Rectangle r = ic.getBounds();
int extraWidth = MIN_WIDTH - r.width;
int extraHeight = MIN_HEIGHT - r.height;
if (extraWidth<=0 && extraHeight<=0 && !Prefs.noBorder && !IJ.isLinux())
g.drawRect(r.x-1, r.y-1, r.width+1, r.height+1);
}
/** Removes this window from the window list and disposes of it.
Returns false if the user cancels the "save changes" dialog. */
public boolean close() {
boolean isRunning = running || running2;
running = running2 = false;
boolean virtual = imp.getStackSize()>1 && imp.getStack().isVirtual();
if (isRunning) IJ.wait(500);
if (ij==null || IJ.getApplet()!=null || Interpreter.isBatchMode() || IJ.macroRunning() || virtual)
imp.changes = false;
if (imp.changes) {
String msg;
String name = imp.getTitle();
if (name.length()>22)
msg = "Save changes to\n" + "\"" + name + "\"?";
else
msg = "Save changes to \"" + name + "\"?";
YesNoCancelDialog d = new YesNoCancelDialog(this, "ImageJ", msg);
if (d.cancelPressed())
return false;
else if (d.yesPressed()) {
FileSaver fs = new FileSaver(imp);
if (!fs.save()) return false;
}
}
closed = true;
if (WindowManager.getWindowCount()==0)
{xloc = 0; yloc = 0;}
WindowManager.removeWindow(this);
//setVisible(false);
if (ij!=null && ij.quitting()) // this may help avoid thread deadlocks
return true;
dispose();
if (imp!=null)
imp.flush();
imp = null;
return true;
}
public ImagePlus getImagePlus() {
return imp;
}
public void setImage(ImagePlus imp2) {
ImageCanvas ic = getCanvas();
if (ic==null || imp2==null)
return;
imp = imp2;
imp.setWindow(this);
ic.updateImage(imp);
ic.setImageUpdated();
ic.repaint();
repaint();
}
public void updateImage(ImagePlus imp) {
if (imp!=this.imp)
throw new IllegalArgumentException("imp!=this.imp");
this.imp = imp;
ic.updateImage(imp);
setLocationAndSize(true);
if (this instanceof StackWindow) {
StackWindow sw = (StackWindow)this;
int stackSize = imp.getStackSize();
int nScrollbars = sw.getNScrollbars();
if (stackSize==1 && nScrollbars>0)
sw.removeScrollbars();
else if (stackSize>1 && nScrollbars==0)
sw.addScrollbars(imp);
}
pack();
repaint();
maxBounds = getMaximumBounds();
setMaximizedBounds(maxBounds);
setMaxBoundsTime = System.currentTimeMillis();
}
public ImageCanvas getCanvas() {
return ic;
}
static ImagePlus getClipboard() {
return ImagePlus.getClipboard();
}
public Rectangle getMaximumBounds() {
double width = imp.getWidth();
double height = imp.getHeight();
double iAspectRatio = width/height;
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
Rectangle maxWindow = ge.getMaximumWindowBounds();
maxWindowBounds = maxWindow;
if (iAspectRatio/((double)maxWindow.width/maxWindow.height)>0.75) {
maxWindow.y += 22; // uncover ImageJ menu bar
maxWindow.height -= 22;
}
Dimension extraSize = getExtraSize();
double maxWidth = maxWindow.width-extraSize.width;
double maxHeight = maxWindow.height-extraSize.height;
double mAspectRatio = maxWidth/maxHeight;
int wWidth, wHeight;
double mag;
if (iAspectRatio>=mAspectRatio) {
mag = maxWidth/width;
wWidth = maxWindow.width;
wHeight = (int)(height*mag+extraSize.height);
} else {
mag = maxHeight/height;
wHeight = maxWindow.height;
wWidth = (int)(width*mag+extraSize.width);
}
int xloc = (int)(maxWidth-wWidth)/2;
if (xloc<0) xloc = 0;
return new Rectangle(xloc, maxWindow.y, wWidth, wHeight);
}
Dimension getExtraSize() {
Insets insets = getInsets();
int extraWidth = insets.left+insets.right + 10;
int extraHeight = insets.top+insets.bottom + 10;
if (extraHeight==20) extraHeight = 42;
int members = getComponentCount();
//if (IJ.debugMode) IJ.log("getExtraSize: "+members+" "+insets);
for (int i=1; i<members; i++) {
Component m = getComponent(i);
Dimension d = m.getPreferredSize();
extraHeight += d.height + 5;
if (IJ.debugMode) IJ.log(i+" "+d.height+" "+extraHeight);
}
return new Dimension(extraWidth, extraHeight);
}
public Component add(Component comp) {
comp = super.add(comp);
maxBounds = getMaximumBounds();
//if (!IJ.isLinux()) {
setMaximizedBounds(maxBounds);
setMaxBoundsTime = System.currentTimeMillis();
//}
return comp;
}
//public void setMaximizedBounds(Rectangle r) {
// super.setMaximizedBounds(r);
// IJ.log("setMaximizedBounds: "+r+" "+getMaximizedBounds());
// if (getMaximizedBounds().x==0)
// throw new IllegalArgumentException("");
//}
public void maximize() {
if (maxBounds==null)
return;
int width = imp.getWidth();
int height = imp.getHeight();
double aspectRatio = (double)width/height;
Dimension extraSize = getExtraSize();
int extraHeight = extraSize.height;
double mag = (double)(maxBounds.height-extraHeight)/height;
if (IJ.debugMode) IJ.log("maximize: "+mag+" "+ic.getMagnification()+" "+maxBounds);
setSize(getMaximizedBounds().width, getMaximizedBounds().height);
if (mag>ic.getMagnification() || aspectRatio<0.5 || aspectRatio>2.0) {
ic.setMagnification2(mag);
ic.setSrcRect(new Rectangle(0, 0, width, height));
ic.setDrawingSize((int)(width*mag), (int)(height*mag));
validate();
unzoomWhenMinimizing = true;
} else
unzoomWhenMinimizing = false;
}
public void minimize() {
if (unzoomWhenMinimizing)
ic.unzoom();
unzoomWhenMinimizing = true;
}
/** Has this window been closed? */
public boolean isClosed() {
return closed;
}
public void focusGained(FocusEvent e) {
//IJ.log("focusGained: "+imp.getTitle());
if (!Interpreter.isBatchMode() && ij!=null && !ij.quitting())
WindowManager.setCurrentWindow(this);
}
public void windowActivated(WindowEvent e) {
if (IJ.debugMode) IJ.log("windowActivated: "+imp.getTitle());
ImageJ ij = IJ.getInstance();
boolean quitting = ij!=null && ij.quitting();
if (IJ.isMacintosh() && ij!=null && !quitting) {
IJ.wait(10); // may be needed for Java 1.4 on OS X
setMenuBar(Menus.getMenuBar());
}
imp.setActivated(); // notify ImagePlus that image has been activated
if (!closed && !quitting && !Interpreter.isBatchMode())
WindowManager.setCurrentWindow(this);
Frame channels = Channels.getInstance();
if (channels!=null && imp.isComposite())
((Channels)channels).update();
}
public void windowClosing(WindowEvent e) {
//IJ.log("windowClosing: "+imp.getTitle()+" "+closed);
if (closed)
return;
if (ij!=null) {
WindowManager.setCurrentWindow(this);
IJ.doCommand("Close");
} else {
//setVisible(false);
dispose();
WindowManager.removeWindow(this);
}
}
public void windowStateChanged(WindowEvent e) {
int oldState = e.getOldState();
int newState = e.getNewState();
//IJ.log("WSC: "+getBounds()+" "+oldState+" "+newState);
if ((oldState & Frame.MAXIMIZED_BOTH) == 0 && (newState & Frame.MAXIMIZED_BOTH) != 0)
maximize();
else if ((oldState & Frame.MAXIMIZED_BOTH) != 0 && (newState & Frame.MAXIMIZED_BOTH) == 0)
minimize();
}
public void windowClosed(WindowEvent e) {}
public void windowDeactivated(WindowEvent e) {}
public void focusLost(FocusEvent e) {}
public void windowDeiconified(WindowEvent e) {}
public void windowIconified(WindowEvent e) {}
public void windowOpened(WindowEvent e) {}
public void mouseWheelMoved(MouseWheelEvent event) {
int rotation = event.getWheelRotation();
int width = imp.getWidth();
int height = imp.getHeight();
Rectangle srcRect = ic.getSrcRect();
int xstart = srcRect.x;
int ystart = srcRect.y;
if (IJ.spaceBarDown() || srcRect.height==height) {
srcRect.x += rotation*Math.max(width/200, 1);
if (srcRect.x<0) srcRect.x = 0;
if (srcRect.x+srcRect.width>width) srcRect.x = width-srcRect.width;
} else {
srcRect.y += rotation*Math.max(height/200, 1);
if (srcRect.y<0) srcRect.y = 0;
if (srcRect.y+srcRect.height>height) srcRect.y = height-srcRect.height;
}
if (srcRect.x!=xstart || srcRect.y!=ystart)
ic.repaint();
}
/** Copies the current ROI to the clipboard. The entire
image is copied if there is no ROI. */
public void copy(boolean cut) {
imp.copy(cut);
}
public void paste() {
imp.paste();
}
/** This method is called by ImageCanvas.mouseMoved(MouseEvent).
@see ij.gui.ImageCanvas#mouseMoved
*/
public void mouseMoved(int x, int y) {
imp.mouseMoved(x, y);
}
public String toString() {
return imp!=null?imp.getTitle():"";
}
/** Causes the next image to be opened to be centered on the screen
and displayed without informational text above the image. */
public static void centerNextImage() {
centerOnScreen = true;
}
/** Causes the next image to be displayed at the specified location. */
public static void setNextLocation(Point loc) {
nextLocation = loc;
}
/** Moves and resizes this window. Changes the
magnification so the image fills the window. */
public void setLocationAndSize(int x, int y, int width, int height) {
setBounds(x, y, width, height);
getCanvas().fitToWindow();
pack();
}
/** Overrides the setBounds() method in Component so
we can find out when the window is resized. */
//public void setBounds(int x, int y, int width, int height) {
// super.setBounds(x, y, width, height);
// ic.resizeSourceRect(width, height);
//}
} //class ImageWindow