/** DocumentFrame.java
 *  class DocumentFrame is a generic document frame with a panning / zooming tool and floating panel support
 *  Samuel Watkins / Lateral Software 1998
 */
package com.lateral.pf;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
// a generic document frame with a panning / zooming tool and toolbar support
public class DocumentFrame extends Frame {
    // instance variables
    public static final Color colBackground = Colors.white;
    public static final int iFPMI = 4; // min distance of floating panels from edge of diagram panel
    public Document d;
    public DocumentPanel dp;
    public ViewPanel vp;
    public Transformer t;      // contains zoom / pan settings
    private Vector vFP;        // vector of floating panels
    // override this method to setup the menu bar and other floating panels
    public void setup() {}
    // constructor
    public DocumentFrame(String title, Document _d) {
        super(title);
        // document
        d = _d;
        d.setDocumentFrame(this);
        // floating panel vector
        vFP = new Vector(8,8);
        // paper color
        setBackground(colBackground);
        // initial position (almost full screen)
        Dimension dimScreen = Toolkit.getDefaultToolkit().getScreenSize();
        setBounds(64, 32, dimScreen.width - 128, dimScreen.height - 128);
        // transformer
        t = new Transformer();
        t.setZoom(8.0);
        Rectangle rIB = getInnerBounds();
        t.setGraphicsOrigin(rIB.width/2, rIB.height/2);
        // composition
        setLayout(null);
        /*
        The components are not created and added here (although they used to be).
        They are created when the frame's peer has been created (via addNotify() below).
        This ensures that the bloody things end up in the right places!
        This could conceivably cause problems if events started coming in before the
        peer was created, but I can't see how that could happen.
        - It would probably be better to create the components here, but size and add them
        in the addNotify() method.
        */
        // handle frame resize
		addComponentListener(
			new ComponentAdapter() {
				public void componentResized(ComponentEvent ev) {
            	    Rectangle rOIB = dp.getBounds();   // old frame inner bounds
            	    Rectangle rNIB = getInnerBounds(); // new frame inner bounds
            	    // resize document panel
            	    dp.setBounds(rNIB);
            	    // alter graphics origin (to center of panel)
                    t.setGraphicsOrigin(rNIB.width/2, rNIB.height/2);
                    // repaint the map panel
                    vp.repaintMap();
                    // move toolbars if necessary - they stick to the edges
                    vp.validatePosition(rOIB, rNIB);
                    for (Enumeration enumFP = vFP.elements(); enumFP.hasMoreElements(); ) {
                        FloatingPanel fp = (FloatingPanel)(enumFP.nextElement());
                        fp.validatePosition(rOIB, rNIB);
                    }
                    // reset double buffering
                    dp.resetDoubleBuffering();
                }
			}
		);
        // show
        setVisible(true);
    }
    // adjust components according to offsets
	boolean bPanelsAdded = false;
	public void addNotify()
	{
		super.addNotify();
		if (!bPanelsAdded) {
            Rectangle rIB = getInnerBounds();
            // document panel setup
            dp = new DocumentPanel();
    	    dp.setBounds(rIB);
    	    // view panel setup
            vp = new ViewPanel(this);
    	    vp.modifyDocumentSize(rIB);
    	    vp.setLocation(
    	        rIB.x + iFPMI,
    	        rIB.y + rIB.height - iFPMI - vp.getSize().height
    	    );
            // add view panel
            add(vp);
            // setup and add other floating panels and menu bar
            setup();
            // add document panel
            add(dp);
    		bPanelsAdded = true;
    	}
	}
    // function to return the bounds of the frame excluding insets
    public Rectangle getInnerBounds() {
	    Rectangle r = getBounds();
	    Insets i = getInsets();
	    r.x = i.left;
	    r.y = i.top;
	    r.width -= (i.left + i.right);
	    r.height -= (i.top + i.bottom);
        return r;
    }
    // method to add a floating panel
    public void addFloatingPanel(FloatingPanel fp) {
        add(fp);
        vFP.addElement(fp);
    }
    // panel to contain the document
    public class DocumentPanel extends Panel {
        // instance variables
        public boolean documentChanged = true;
        private Image imageDB = null;   // image for double buffering
        // constructor
        public DocumentPanel() {
            // background color
            setBackground(d.getPaperColor());
        }
        // reset the double buffering (e.g. when the DocumentFrame is resized)
        public void resetDoubleBuffering() {
            imageDB = null; // the new image is created when the paint method is called
        }
            
        // paint the document panel
        public void update(Graphics gScreen) {
            paint(gScreen);
        }
        public void paint(Graphics gScreen) {
            // setup double buffering
	        Dimension dim = getSize();
            if (imageDB == null) {
                // create a new double buffering image
                documentChanged = true;
                imageDB = createImage(dim.width, dim.height);
            }
            if (documentChanged) {
                // repaint (part of the) double buffered image
                Graphics g = imageDB.getGraphics();
                Rectangle rB = gScreen.getClipBounds();
                g.clipRect(rB.x, rB.y, rB.width, rB.height);
                // clear background
                g.clearRect(rB.x, rB.y, rB.width, rB.height);
                // setup transformed graphics
    	        DRectangle drCB = t.dr(rB);
    	        t.setGraphics(g);
                // draw diagram border
          		double pwot = d.width/2, phot = d.height/2; // page width and height over two!
          		t.g.setColor(colBackground);
                if (drCB.x <= -pwot) {
                    double y = Math.max(drCB.y, -phot);
                    t.fillRect(drCB.x, y, -pwot - drCB.x, Math.min(drCB.y + drCB.height, phot) - y);
                }
                if (drCB.x + drCB.width >= pwot) {
                    double y = Math.max(drCB.y, -phot);
                    t.fillRect(pwot, y, drCB.x + drCB.width - pwot, Math.min(drCB.y + drCB.height, phot) - y);
                }
                if (drCB.y <= -phot) {
                    t.fillRect(drCB.x, drCB.y, drCB.width, -phot - drCB.y);
                }
                if (drCB.y + drCB.height >= phot) {
                    t.fillRect(drCB.x, phot, drCB.width, drCB.y + drCB.height - phot);
                }
                t.g.setColor(Color.gray);
          		t.drawRect(-pwot, -phot, d.width, d.height);
                // paint document
    	        d.paint(t, drCB, false);
    	        // draw white edge around document
    	        g.setColor(Colors.white);
    	        g.drawRect(0, 0, dim.width-1, dim.height-1);
    	        documentChanged = false;
    	    }
            // paint double buffered image to screen
            gScreen.drawImage(imageDB, 0, 0, null, null);
        }
    }
    // repaint the whole document
    public void repaintDocument() {
        dp.documentChanged = true;
        dp.repaint();
        vp.repaintMap();
    }
    // repaint part of the document (given in document units)
    public void repaintDocument(DRectangle dr) {
        dp.documentChanged = true;
        Rectangle gr = t.gr(dr);
   	    dp.repaint(gr.x, gr.y, gr.width+2, gr.height+2);
        vp.repaintMap(dr);
    }
}
