/*******************************************************************************
 * Copyright (c) 2000, 2004 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui.internal.presentations.r21.widgets;

import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.Listener;

/**
 * Instances of this class implement a Composite that lays out three
 * children horizontally and allows programmatic control of layout and
 * border parameters. ViewForm is used in the workbench to implement a
 * view's label/menu/toolbar local bar.
 * <p>
 * Note that although this class is a subclass of <code>Composite</code>,
 * it does not make sense to set a layout on it.
 * </p><p>
 * <dl>
 * <dt><b>Styles:</b></dt>
 * <dd>BORDER, FLAT</dd>
 * <dt><b>Events:</b></dt>
 * <dd>(None)</dd>
 * </dl>
 * <p>
 * IMPORTANT: This class is <em>not</em> intended to be subclassed.
 * </p>
 */

public class ViewForm extends Composite {

    /**
     * marginWidth specifies the number of pixels of horizontal margin
     * that will be placed along the left and right edges of the form.
     *
     * The default value is 0.
     */
    public int marginWidth = 0;

    /**
     * marginHeight specifies the number of pixels of vertical margin
     * that will be placed along the top and bottom edges of the form.
     *
     * The default value is 0.
     */
    public int marginHeight = 0;

    /**
     * Color of innermost line of drop shadow border.
     */
    public static RGB borderInsideRGB = new RGB(132, 130, 132);

    /**
     * Color of middle line of drop shadow border.
     */
    public static RGB borderMiddleRGB = new RGB(143, 141, 138);

    /**
     * Color of outermost line of drop shadow border.
     */
    public static RGB borderOutsideRGB = new RGB(171, 168, 165);

    // SWT widgets
    private Control topLeft;

    private Control topCenter;

    private Control topRight;

    private Control content;

    // Configuration and state info
    private boolean separateTopCenter = false;

    private int drawLine1 = -1;

    private int drawLine2 = -1;

    private boolean showBorder = false;

    private int BORDER_TOP = 0;

    private int BORDER_BOTTOM = 0;

    private int BORDER_LEFT = 0;

    private int BORDER_RIGHT = 0;

    private Color borderColor1;

    private Color borderColor2;

    private Color borderColor3;

    private Rectangle oldArea;

    private static final int OFFSCREEN = -200;

    /**
     * Constructs a new instance of this class given its parent
     * and a style value describing its behavior and appearance.
     * <p>
     * The style value is either one of the style constants defined in
     * class <code>SWT</code> which is applicable to instances of this
     * class, or must be built by <em>bitwise OR</em>'ing together 
     * (that is, using the <code>int</code> "|" operator) two or more
     * of those <code>SWT</code> style constants. The class description
     * lists the style constants that are applicable to the class.
     * Style bits are also inherited from superclasses.
     * </p>
     *
     * @param parent a widget which will be the parent of the new instance (cannot be null)
     * @param style the style of widget to construct
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
     * </ul>
     *
     * @see SWT#BORDER
     * @see SWT#FLAT
     * @see #getStyle()
     */
    public ViewForm(Composite parent, int style) {
        super(parent, checkStyle(style));

        borderColor1 = new Color(getDisplay(), borderInsideRGB);
        borderColor2 = new Color(getDisplay(), borderMiddleRGB);
        borderColor3 = new Color(getDisplay(), borderOutsideRGB);
        setBorderVisible((style & SWT.BORDER) != 0);

        addPaintListener(new PaintListener() {
            public void paintControl(PaintEvent event) {
                onPaint(event.gc);
            }
        });
        addControlListener(new ControlAdapter() {
            public void controlResized(ControlEvent e) {
                onResize();
            }
        });

        addListener(SWT.Dispose, new Listener() {
            public void handleEvent(Event e) {
                onDispose();
            }
        });
    }

    /**
     * Check the style bits to ensure that no invalid styles are applied.
     * @private
     */
    private static int checkStyle(int style) {
        int mask = SWT.FLAT | SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT;
        return style & mask | SWT.NO_REDRAW_RESIZE;
    }

    public Point computeSize(int wHint, int hHint, boolean changed) {
        checkWidget();
        // size of title bar area
        Point leftSize = new Point(0, 0);
        if (topLeft != null) {
            leftSize = topLeft.computeSize(SWT.DEFAULT, SWT.DEFAULT);
            leftSize.x += 1; // +1 for highlight line
        }
        Point centerSize = new Point(0, 0);
        if (topCenter != null) {
            centerSize = topCenter.computeSize(SWT.DEFAULT, SWT.DEFAULT);
        }
        Point rightSize = new Point(0, 0);
        if (topRight != null) {
            rightSize = topRight.computeSize(SWT.DEFAULT, SWT.DEFAULT);
        }
        Point size = new Point(0, 0);
        // calculate width of title bar
        if (separateTopCenter
                || (wHint != SWT.DEFAULT && leftSize.x + centerSize.x
                        + rightSize.x > wHint)) {
            size.x = leftSize.x + rightSize.x;
            size.x = Math.max(centerSize.x, size.x);
            size.y = Math.max(leftSize.y, rightSize.y) + 1; // +1 for highlight line
            if (topCenter != null) {
                size.y += centerSize.y;
            }
        } else {
            size.x = leftSize.x + centerSize.x + rightSize.x;
            size.y = Math.max(leftSize.y, Math.max(centerSize.y, rightSize.y)) + 1; // +1 for highlight line
        }

        if (content != null) {
            Point contentSize = new Point(0, 0);
            contentSize = content.computeSize(SWT.DEFAULT, SWT.DEFAULT);
            size.x = Math.max(size.x, contentSize.x);
            size.y += contentSize.y + 1; // +1 for line bewteen content and header
        }

        size.x += 2 * marginWidth;
        size.y += 2 * marginHeight;

        if (wHint != SWT.DEFAULT)
            size.x = wHint;
        if (hHint != SWT.DEFAULT)
            size.y = hHint;

        Rectangle trim = computeTrim(0, 0, size.x, size.y);
        return new Point(trim.width, trim.height);
    }

    public Rectangle computeTrim(int x, int y, int width, int height) {
        checkWidget();
        int trimX = x - BORDER_LEFT;
        int trimY = y - BORDER_TOP;
        int trimWidth = width + BORDER_LEFT + BORDER_RIGHT;
        int trimHeight = height + BORDER_TOP + BORDER_BOTTOM;
        return new Rectangle(trimX, trimY, trimWidth, trimHeight);
    }

    public Rectangle getClientArea() {
        checkWidget();
        Rectangle clientArea = super.getClientArea();
        clientArea.x += BORDER_LEFT;
        clientArea.y += BORDER_TOP;
        clientArea.width -= BORDER_LEFT + BORDER_RIGHT;
        clientArea.height -= BORDER_TOP + BORDER_BOTTOM;
        return clientArea;
    }

    /**
     * Returns the content area.
     * 
     * @return the control in the content area of the pane or null
     */
    public Control getContent() {
        //checkWidget();
        return content;
    }

    /**
     * Returns Control that appears in the top center of the pane.
     * Typically this is a toolbar.
     * 
     * @return the control in the top center of the pane or null
     */
    public Control getTopCenter() {
        //checkWidget();
        return topCenter;
    }

    /**
     * Returns the Control that appears in the top left corner of the pane.
     * Typically this is a label such as CLabel.
     * 
     * @return the control in the top left corner of the pane or null
     */
    public Control getTopLeft() {
        //checkWidget();
        return topLeft;
    }

    /**
     * Returns the control in the top right corner of the pane.
     * Typically this is a Close button or a composite with a Menu and Close button.
     * 
     * @return the control in the top right corner of the pane or null
     */
    public Control getTopRight() {
        //checkWidget();
        return topRight;
    }

    public void layout(boolean changed) {
        checkWidget();
        Rectangle rect = getClientArea();

        drawLine1 = -1;
        drawLine2 = -1;

        Point leftSize = new Point(0, 0);
        if (topLeft != null && !topLeft.isDisposed()) {
            leftSize = topLeft.computeSize(SWT.DEFAULT, SWT.DEFAULT);
        }
        Point centerSize = new Point(0, 0);
        if (topCenter != null && !topCenter.isDisposed()) {
            centerSize = topCenter.computeSize(SWT.DEFAULT, SWT.DEFAULT);
        }
        Point rightSize = new Point(0, 0);
        if (topRight != null && !topRight.isDisposed()) {
            rightSize = topRight.computeSize(SWT.DEFAULT, SWT.DEFAULT);
        }

        int minTopWidth = leftSize.x + centerSize.x + rightSize.x + 2
                * marginWidth + 1; // +1 for highlight line	
        int height = rect.y + marginHeight;

        boolean top = false;
        if (separateTopCenter || minTopWidth > rect.width) {
            int topHeight = Math.max(rightSize.y, leftSize.y);
            if (topRight != null && !topRight.isDisposed()) {
                top = true;
                topRight.setBounds(rect.x + rect.width - marginWidth
                        - rightSize.x, rect.y + 1 + marginHeight, rightSize.x,
                        topHeight);
                height += 1 + topHeight; // +1 for highlight line
            }
            if (topLeft != null && !topLeft.isDisposed()) {
                top = true;
                leftSize = topLeft.computeSize(rect.width - 2 * marginWidth
                        - rightSize.x - 1, SWT.DEFAULT);
                topLeft.setBounds(rect.x + 1 + marginWidth, rect.y + 1
                        + marginHeight, leftSize.x, topHeight);
                height = Math
                        .max(height, rect.y + marginHeight + 1 + topHeight); // +1 for highlight line
            }
            if (topCenter != null && !topCenter.isDisposed()) {
                top = true;
                if (height > rect.y + marginHeight) {
                    drawLine1 = height;
                    height += 1; // +1 for divider line
                }
                centerSize = topCenter.computeSize(
                        rect.width - 2 * marginWidth, SWT.DEFAULT);
                topCenter.setBounds(rect.x + rect.width - marginWidth
                        - centerSize.x, height, centerSize.x, centerSize.y);
                height += centerSize.y;

            }
        } else {
            int topHeight = Math.max(rightSize.y, Math.max(centerSize.y,
                    leftSize.y));
            if (topRight != null && !topRight.isDisposed()) {
                top = true;
                topRight.setBounds(rect.x + rect.width - marginWidth
                        - rightSize.x, rect.y + marginHeight + 1, // +1 for highlight line
                        rightSize.x, topHeight);
                height += 1 + topHeight; // +1 for highlight line
            }
            if (topCenter != null && !topCenter.isDisposed()) {
                top = true;
                topCenter.setBounds(rect.x + rect.width - marginWidth
                        - rightSize.x - centerSize.x,
                        rect.y + marginHeight + 1, // +1 for highlight line
                        centerSize.x, topHeight);
                height = Math
                        .max(height, rect.y + marginHeight + 1 + topHeight); // +1 for highlight line                    
            }
            if (topLeft != null && !topLeft.isDisposed()) {
                top = true;
                leftSize = topLeft.computeSize(rect.width - 2 * marginWidth
                        - rightSize.x - centerSize.x - 1, topHeight);
                topLeft.setBounds(rect.x + marginWidth + 1, // +1 for highlight line
                        rect.y + marginHeight + 1, // +1 for highlight line
                        leftSize.x, topHeight);
                height = Math
                        .max(height, rect.y + marginHeight + 1 + topHeight); // +1 for highlight line
            }
        }

        if (content != null && !content.isDisposed()) {
            if (top) {
                drawLine2 = height;
                height += 1; // +1 for divider line
            }
            content
                    .setBounds(rect.x + marginWidth, height, rect.width - 2
                            * marginWidth, rect.y + rect.height - height
                            - marginHeight);
        }
    }

    private void onDispose() {
        if (borderColor1 != null) {
            borderColor1.dispose();
        }
        borderColor1 = null;

        if (borderColor2 != null) {
            borderColor2.dispose();
        }
        borderColor2 = null;

        if (borderColor3 != null) {
            borderColor3.dispose();
        }
        borderColor3 = null;

        topLeft = null;
        topCenter = null;
        topRight = null;
        content = null;
        oldArea = null;
    }

    /**
     * Draws the focus border.
     */
    private void onPaint(GC gc) {
        Rectangle d = super.getClientArea();

        if (showBorder) {
            if ((getStyle() & SWT.FLAT) != 0) {
                gc.setForeground(borderColor1);
                gc.drawRectangle(d.x, d.y, d.x + d.width - 1, d.y + d.height
                        - 1);
            } else {
                gc.setForeground(borderColor1);
                gc.drawRectangle(d.x, d.y, d.x + d.width - 3, d.y + d.height
                        - 3);

                gc.setForeground(borderColor2);
                gc.drawLine(d.x + 1, d.y + d.height - 2, d.x + d.width - 1, d.y
                        + d.height - 2);
                gc.drawLine(d.x + d.width - 2, d.y + 1, d.x + d.width - 2, d.y
                        + d.height - 1);

                gc.setForeground(borderColor3);
                gc.drawLine(d.x + 2, d.y + d.height - 1, d.x + d.width - 2, d.y
                        + d.height - 1);
                gc.drawLine(d.x + d.width - 1, d.y + 2, d.x + d.width - 1, d.y
                        + d.height - 2);
            }
        }

        if (drawLine1 != -1) {
            // top seperator line
            gc.setForeground(borderColor1);
            gc.drawLine(d.x + BORDER_LEFT, drawLine1, d.x + d.width
                    - BORDER_RIGHT, drawLine1);
        }
        if (drawLine2 != -1) {
            // content separator line
            gc.setForeground(borderColor1);
            gc.drawLine(d.x + BORDER_LEFT, drawLine2, d.x + d.width
                    - BORDER_RIGHT, drawLine2);
        }
        // highlight on top
        int y = drawLine1;
        if (y == -1) {
            y = drawLine2;
        }
        if (y != -1) {
            gc.setForeground(getDisplay().getSystemColor(
                    SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW));
            gc.drawLine(d.x + BORDER_LEFT + marginWidth, d.y + BORDER_TOP
                    + marginHeight, d.x + BORDER_LEFT + marginWidth, y - 1);
            gc.drawLine(d.x + BORDER_LEFT + marginWidth, d.y + BORDER_TOP
                    + marginHeight, d.x + d.width - BORDER_RIGHT - marginWidth
                    - 1, d.y + BORDER_TOP + marginHeight);
        }

        gc.setForeground(getForeground());
    }

    private void onResize() {
        layout();

        Rectangle area = super.getClientArea();
        if (oldArea == null || oldArea.width == 0 || oldArea.height == 0) {
            redraw();
        } else {
            int width = 0;
            if (oldArea.width < area.width) {
                width = area.width - oldArea.width + BORDER_RIGHT;
            } else if (oldArea.width > area.width) {
                width = BORDER_RIGHT;
            }
            redraw(area.x + area.width - width, area.y, width, area.height,
                    false);

            int height = 0;
            if (oldArea.height < area.height) {
                height = area.height - oldArea.height + BORDER_BOTTOM;
            }
            if (oldArea.height > area.height) {
                height = BORDER_BOTTOM;
            }
            redraw(area.x, area.y + area.height - height, area.width, height,
                    false);
        }
        oldArea = area;
    }

    /**
     * Sets the content.
     * Setting the content to null will remove it from 
     * the pane - however, the creator of the content must dispose of the content.
     * 
     * @param content the control to be displayed in the content area or null
     * 
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setContent(Control content) {
        checkWidget();
        if (content != null && content.getParent() != this) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        if (this.content != null && !this.content.isDisposed()) {
            this.content.setBounds(OFFSCREEN, OFFSCREEN, 0, 0);
        }
        this.content = content;
        layout();
    }

    /**
     * Set the widget font.
     * This will apply the font to the topLeft, topRight and topCenter widgets.
     */
    public void setFont(Font f) {
        super.setFont(f);
        if (topLeft != null && !topLeft.isDisposed())
            topLeft.setFont(f);
        if (topCenter != null && !topCenter.isDisposed())
            topCenter.setFont(f);
        if (topRight != null && !topRight.isDisposed())
            topRight.setFont(f);

        layout();
    }

    /**
     * Sets the layout which is associated with the receiver to be
     * the argument which may be null.
     * <p>
     * Note : ViewForm does not use a layout class to size and position its children.
     * </p>
     *
     * @param layout the receiver's new layout or null
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setLayout(Layout layout) {
        checkWidget();
        return;
    }

    /**
     * Set the control that appears in the top center of the pane.
     * Typically this is a toolbar.
     * The topCenter is optional.  Setting the topCenter to null will remove it from 
     * the pane - however, the creator of the topCenter must dispose of the topCenter.
     * 
     * @param topCenter the control to be displayed in the top center or null
     * 
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setTopCenter(Control topCenter) {
        checkWidget();
        if (topCenter != null && topCenter.getParent() != this) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        if (this.topCenter != null && !this.topCenter.isDisposed()) {
            this.topCenter.setBounds(OFFSCREEN, OFFSCREEN, 0, 0);
        }
        this.topCenter = topCenter;
        layout();
    }

    /**
     * Set the control that appears in the top left corner of the pane.
     * Typically this is a label such as CLabel.
     * The topLeft is optional.  Setting the top left control to null will remove it from 
     * the pane - however, the creator of the control must dispose of the control.
     * 
     * @param c the control to be displayed in the top left corner or null
     * 
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setTopLeft(Control c) {
        checkWidget();
        if (c != null && c.getParent() != this) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        if (this.topLeft != null && !this.topLeft.isDisposed()) {
            this.topLeft.setBounds(OFFSCREEN, OFFSCREEN, 0, 0);
        }
        this.topLeft = c;
        layout();
    }

    /**
     * Set the control that appears in the top right corner of the pane.
     * Typically this is a Close button or a composite with a Menu and Close button.
     * The topRight is optional.  Setting the top right control to null will remove it from 
     * the pane - however, the creator of the control must dispose of the control.
     * 
     * @param c the control to be displayed in the top right corner or null
     * 
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     *    <li>ERROR_INVALID_ARGUMENT - if the control is not a child of this ViewForm</li>
     * </ul>
     */
    public void setTopRight(Control c) {
        checkWidget();
        if (c != null && c.getParent() != this) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        if (this.topRight != null && !this.topRight.isDisposed()) {
            this.topRight.setBounds(OFFSCREEN, OFFSCREEN, 0, 0);
        }
        this.topRight = c;
        layout();
    }

    /**
     * Specify whether the border should be displayed or not.
     * 
     * @param show true if the border should be displayed
     * 
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setBorderVisible(boolean show) {
        checkWidget();
        if (showBorder == show)
            return;

        showBorder = show;
        if (showBorder) {
            if ((getStyle() & SWT.FLAT) != 0) {
                BORDER_LEFT = BORDER_TOP = BORDER_RIGHT = BORDER_BOTTOM = 1;
            } else {
                BORDER_LEFT = BORDER_TOP = 1;
                BORDER_RIGHT = BORDER_BOTTOM = 3;
            }
        } else {
            BORDER_BOTTOM = BORDER_TOP = BORDER_LEFT = BORDER_RIGHT = 0;
        }

        layout();
        redraw();
    }

    /**
     * If true, the topCenter will always appear on a separate line by itself, otherwise the 
     * topCenter will appear in the top row if there is room and will be moved to the second row if
     * required.
     * 
     * @param show true if the topCenter will always appear on a separate line by itself
     * 
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setTopCenterSeparate(boolean show) {
        checkWidget();
        separateTopCenter = show;
        layout();
    }

}
