| ImageWindow |

    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.Recorder;
    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 static Rectangle maxWindow;
        private boolean unzoomWhenMinimizing = true;
        Rectangle maxBounds;

        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 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);
            }
            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();
                }
                boolean unlocked = imp.lockSilently();
                boolean changes = imp.changes;
                imp.changes = false;
                previousWindow.close();
                imp.changes = changes;
                if (unlocked)
                    imp.unlock();
                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;
                }
                if (Interpreter.isBatchMode()) {
                    WindowManager.setTempCurrentImage(imp);
                    Interpreter.addBatchModeImage(imp);
                } else
                    show();
            }
         }

        private void setLocationAndSize(boolean updating) {
            int width = imp.getWidth();
            int height = imp.getHeight();
            if (maxWindow==null)
                maxWindow = getMaxWindow();
            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.height-sliderHeight;
            double mag = 1;
            while (xbase+XINC*4+width*mag>maxWindow.width || ybase+height*mag>screenHeight) {
                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();
            maxBounds = getMaximumBounds();
            if (!IJ.isLinux())
                setMaximizedBounds(maxBounds);
        }

        Rectangle getMaxWindow() {
            GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
            Rectangle maxWindow = ge.getMaximumWindowBounds();
            Dimension ijSize = ij!=null?ij.getSize():new Dimension(0,0);
            maxWindow.y += ijSize.height;
            maxWindow.height -= ijSize.height;
            return maxWindow;
        }

        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 instanceof CompositeImage)
                    g.setColor(((CompositeImage)imp).getChannelColor());
                g.drawString(createSubtitle(), 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;
                boolean isLabel = false;
                String label = stack.getShortSliceLabel(currentSlice);
                if (label!=null && label.length()>0)
                    s += " (" + label + ")";
                if ((this instanceof StackWindow) && running2) {
                    return s;
                }
                s += "; ";
            }

            int type = imp.getType();
            Calibration cal = imp.getCalibration();
            if (cal.pixelWidth!=1.0 || cal.pixelHeight!=1.0)
                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; ";
            int size = (imp.getWidth()*imp.getHeight()*imp.getStackSize())/1024;
            switch (type) {
                case ImagePlus.GRAY8:
                case ImagePlus.COLOR_256:
                    s += "8-bit";
                    break;
                case ImagePlus.GRAY16:
                    s += "16-bit";
                    size *= 2;
                    break;
                case ImagePlus.GRAY32:
                    s += "32-bit";
                    size *= 4;
                    break;
                case ImagePlus.COLOR_RGB:
                    s += "RGB";
                    size *= 4;
                    break;
            }
            if (imp.isInvertedLut())
                s += " (inverting LUT)";
            if (size>=10000)
                s += "; " + (int)Math.round(size/1024.0) + "MB";
            else if (size>=1024) {
                double size2 = size/1024.0;
                s += "; " + IJ.d2s(size2,(int)size2==size2?0:1) + "MB";
            } else
                s += "; " + size + "K";
            return s;
        }

        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;
            if (isRunning) IJ.wait(500);
            if (ij==null || IJ.getApplet()!=null || Interpreter.isBatchMode() || IJ.macroRunning())
                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();
            imp.flush();
            return true;
        }

        public ImagePlus getImagePlus() {
            return imp;
        }

        void setImagePlus(ImagePlus imp) {
            this.imp = imp;
            repaint();
        }

        public void updateImage(ImagePlus imp) {
            if (imp!=this.imp)
                throw new IllegalArgumentException("imp!=this.imp");
            this.imp = imp;
            ic.updateImage(imp);
            setLocationAndSize(true);
            pack();
            repaint();
        }

        public ImageCanvas getCanvas() {
            return ic;
        }

        static ImagePlus getClipboard() {
            return ImagePlus.getClipboard();
        }

        public Rectangle getMaximumBounds() {
            int width = imp.getWidth();
            int height = imp.getHeight();
            maxWindow = getMaxWindow();
            Insets insets = getInsets();
            int extraHeight = insets.top+insets.bottom;
            if (this instanceof StackWindow) extraHeight += 25;
            //if (IJ.isWindows()) extraHeight += 20;
            double maxHeight = maxWindow.height-extraHeight;
            double maxWidth = maxWindow.width;
            double mAspectRatio = maxWidth/maxHeight;
            double iAspectRatio = (double)width/height;
            int wWidth, wHeight;
            if (iAspectRatio>=mAspectRatio) {
                wWidth = (int)maxWidth;
                wHeight = (int)(maxWidth/iAspectRatio);
            } else {
                wHeight = (int)maxHeight;
                wWidth = (int)(maxHeight*iAspectRatio);
            }
            int xloc = (int)(maxWidth-wWidth)/2;
            if (xloc<0) xloc = 0;
            return new Rectangle(xloc, maxWindow.y, wWidth, wHeight+extraHeight);
        }

        public void maximize() {
            if (maxBounds==null) return;
            int width = imp.getWidth();
            int height = imp.getHeight();
            Insets insets = getInsets();
            int extraHeight = insets.top+insets.bottom+5;
            if (this instanceof StackWindow) extraHeight += 25;
            double mag = Math.floor((maxBounds.height-extraHeight)*100.0/height)/100.0;
            double aspectRatio = (double)width/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) {
            //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);
        }

        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.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;
        }

        /** 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

| ImageWindow |